CM3D2 Converter.common
1import os 2import re 3import math 4import struct 5import shutil 6import bpy 7import bmesh 8import mathutils 9from . import fileutil 10from . import compat 11from . import cm3d2_data 12 13# アドオン情報 14bl_info = {} 15ADDON_NAME = "CM3D2 Converter" 16BASE_PATH_TEX = "Assets/texture/texture/" 17BRANCH = "bl_28" 18URL_REPOS = "https://github.com/luvoid/Blender-CM3D2-Converter/" 19URL_ATOM = URL_REPOS + "commits/{branch}.atom" 20URL_MODULE = URL_REPOS + "archive/{branch}.zip" 21KISS_ICON = None 22PREFS = None 23preview_collections = {} 24texpath_dict = {} 25 26 27re_png = re.compile(r"\.[Pp][Nn][Gg](\.\d{3})?$") 28re_serial = re.compile(r"(\.\d{3})$") 29re_prefix = re.compile(r"^[\/\.]*") 30re_path_prefix = re.compile(r"^assets/", re.I) 31re_ext_png = re.compile(r"\.png$", re.I) 32re_bone1 = re.compile(r"([_ ])\*([_ ].*)\.([rRlL])$") 33re_bone2 = re.compile(r"([_ ])([rRlL])([_ ].*)$") 34 35 36# このアドオンの設定値群を呼び出す 37def preferences(): 38 global PREFS 39 if PREFS is None: 40 PREFS = compat.get_prefs(bpy.context).addons[__package__].preferences 41 return PREFS 42 43 44def kiss_icon(): 45 global KISS_ICON 46 if KISS_ICON is None: 47 KISS_ICON = preview_collections['main']['KISS'].icon_id 48 return KISS_ICON 49 50 51# データ名末尾の「.001」などを削除 52def remove_serial_number(name, enable=True): 53 return re_serial.sub('', name) if enable else name 54 55 56# データ名末尾の「.001」などが含まれるか判定 57def has_serial_number(name): 58 return re_serial.search(name) is not None 59 60 61# 文字列の左右端から空白を削除 62def line_trim(line, enable=True): 63 return line.strip(' \t\r\n') if enable else line 64 65 66# CM3D2専用ファイル用の文字列書き込み 67def write_str(file, raw_str): 68 b_str = format(len(raw_str.encode('utf-8')), 'b') 69 for i in range(9): 70 if 7 < len(b_str): 71 file.write(struct.pack('<B', int("1" + b_str[-7:], 2))) 72 b_str = b_str[:-7] 73 else: 74 file.write(struct.pack('<B', int(b_str, 2))) 75 break 76 file.write(raw_str.encode('utf-8')) 77 78def pack_str(buffer, raw_str): 79 b_str = format(len(raw_str.encode('utf-8')), 'b') 80 for i in range(9): 81 if 7 < len(b_str): 82 buffer = buffer + struct.pack('<B', int("1" + b_str[-7:], 2)) 83 b_str = b_str[:-7] 84 else: 85 buffer = buffer + struct.pack('<B', int(b_str, 2)) 86 break 87 buffer = buffer + raw_str.encode('utf-8') 88 return buffer 89 90 91# CM3D2専用ファイル用の文字列読み込み 92def read_str(file, total_b=""): 93 for i in range(9): 94 b_str = format(struct.unpack('<B', file.read(1))[0], '08b') 95 total_b = b_str[1:] + total_b 96 if b_str[0] == '0': 97 break 98 return file.read(int(total_b, 2)).decode('utf-8') 99 100 101# ボーン/ウェイト名を Blender → CM3D2 102def encode_bone_name(name, enable=True): 103 return re.sub(r'([_ ])\*([_ ].*)\.([rRlL])$', r'\1\3\2', name) if enable and name.count('*') == 1 else name 104 105 106# ボーン/ウェイト名を CM3D2 → Blender 107def decode_bone_name(name, enable=True): 108 return re.sub(r'([_ ])([rRlL])([_ ].*)$', r'\1*\3.\2', name) if enable else name 109 110 111# CM3D2用マテリアルを設定に合わせて装飾 112def decorate_material_old(mate, enable=True, me=None, mate_index=-1): 113 if not compat.IS_LEGACY or not enable or 'shader1' not in mate: 114 return 115 116 shader = mate['shader1'] 117 if 'CM3D2/Man' == shader: 118 mate.use_shadeless = True 119 mate.diffuse_color = (0, 1, 1) 120 elif 'CM3D2/Mosaic' == shader: 121 mate.use_transparency = True 122 mate.transparency_method = 'RAYTRACE' 123 mate.alpha = 0.25 124 mate.raytrace_transparency.ior = 2 125 elif 'CM3D2_Debug/Debug_CM3D2_Normal2Color' == shader: 126 mate.use_tangent_shading = True 127 mate.diffuse_color = (0.5, 0.5, 1) 128 129 else: 130 if '/Toony_' in shader: 131 mate.diffuse_shader = 'TOON' 132 mate.diffuse_toon_smooth = 0.01 133 mate.diffuse_toon_size = 1.2 134 if 'Trans' in shader: 135 mate.use_transparency = True 136 mate.alpha = 0.0 137 mate.texture_slots[0].use_map_alpha = True 138 if 'Unlit/' in shader: 139 mate.emit = 0.5 140 if '_NoZ' in shader: 141 mate.offset_z = 9999 142 143 is_colored = False 144 is_textured = [False, False, False, False] 145 rimcolor, rimpower, rimshift = mathutils.Color((1, 1, 1)), 0.0, 0.0 146 for slot in mate.texture_slots: 147 if not slot or not slot.texture: 148 continue 149 150 tex = slot.texture 151 tex_name = remove_serial_number(tex.name) 152 slot.use_map_color_diffuse = False 153 154 if tex_name == '_MainTex': 155 slot.use_map_color_diffuse = True 156 img = getattr(tex, 'image') 157 if img and len(img.pixels): 158 if me: 159 color = mathutils.Color(get_image_average_color_uv(img, me, mate_index)[:3]) 160 else: 161 color = mathutils.Color(get_image_average_color(img)[:3]) 162 mate.diffuse_color = color 163 is_colored = True 164 165 elif tex_name == '_RimColor': 166 rimcolor = slot.color[:] 167 if not is_colored: 168 mate.diffuse_color = slot.color[:] 169 mate.diffuse_color.v += 0.5 170 171 elif tex_name == '_Shininess': 172 mate.specular_intensity = slot.diffuse_color_factor 173 174 elif tex_name == '_RimPower': 175 rimpower = slot.diffuse_color_factor 176 177 elif tex_name == '_RimShift': 178 rimshift = slot.diffuse_color_factor 179 180 for index, name in enumerate(['_MainTex', '_ToonRamp', '_ShadowTex', '_ShadowRateToon']): 181 if tex_name == name: 182 img = getattr(tex, 'image') 183 if img and len(tex.image.pixels): 184 is_textured[index] = tex 185 186 set_texture_color(slot) 187 188 # よりオリジナルに近く描画するノード作成 189 if all(is_textured): 190 mate.use_nodes = True 191 mate.use_shadeless = True 192 193 node_tree = mate.node_tree 194 for node in node_tree.nodes[:]: 195 node_tree.nodes.remove(node) 196 197 mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial') 198 mate_node.location = (0, 0) 199 mate_node.material = mate 200 201 if "CM3D2 Shade" in bpy.context.blend_data.materials: 202 shade_mate = bpy.context.blend_data.materials["CM3D2 Shade"] 203 else: 204 shade_mate = bpy.context.blend_data.materials.new("CM3D2 Shade") 205 shade_mate.diffuse_color = (1, 1, 1) 206 shade_mate.diffuse_intensity = 1 207 shade_mate.specular_intensity = 1 208 shade_mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial') 209 shade_mate_node.location = (234.7785, -131.8243) 210 shade_mate_node.material = shade_mate 211 212 toon_node = node_tree.nodes.new('ShaderNodeValToRGB') 213 toon_node.location = (571.3662, -381.0965) 214 toon_img = is_textured[1].image 215 toon_w, toon_h = toon_img.size[0], toon_img.size[1] 216 for i in range(32 - 2): 217 toon_node.color_ramp.elements.new(0.0) 218 for i in range(32): 219 pos = i / (32 - 1) 220 toon_node.color_ramp.elements[i].position = pos 221 x = int((toon_w / (32 - 1)) * i) 222 pixel_index = x * toon_img.channels 223 toon_node.color_ramp.elements[i].color = toon_img.pixels[pixel_index: pixel_index + 4] 224 toon_node.color_ramp.interpolation = 'EASE' 225 226 shadow_rate_node = node_tree.nodes.new('ShaderNodeValToRGB') 227 shadow_rate_node.location = (488.2785, 7.8446) 228 shadow_rate_img = is_textured[3].image 229 shadow_rate_w, shadow_rate_h = shadow_rate_img.size[0], shadow_rate_img.size[1] 230 for i in range(32 - 2): 231 shadow_rate_node.color_ramp.elements.new(0.0) 232 for i in range(32): 233 pos = i / (32 - 1) 234 shadow_rate_node.color_ramp.elements[i].position = pos 235 x = int((shadow_rate_w / (32)) * i) 236 pixel_index = x * shadow_rate_img.channels 237 shadow_rate_node.color_ramp.elements[i].color = shadow_rate_img.pixels[pixel_index: pixel_index + 4] 238 shadow_rate_node.color_ramp.interpolation = 'EASE' 239 240 geometry_node = node_tree.nodes.new('ShaderNodeGeometry') 241 geometry_node.location = (323.4597, -810.8045) 242 243 shadow_texture_node = node_tree.nodes.new('ShaderNodeTexture') 244 shadow_texture_node.location = (626.0117, -666.0227) 245 shadow_texture_node.texture = is_textured[2] 246 247 invert_node = node_tree.nodes.new('ShaderNodeInvert') 248 invert_node.location = (805.6814, -132.9144) 249 250 shadow_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 251 shadow_mix_node.location = (1031.2714, -201.5598) 252 253 toon_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 254 toon_mix_node.location = (1257.5538, -308.8037) 255 toon_mix_node.blend_type = 'MULTIPLY' 256 toon_mix_node.inputs[0].default_value = 1.0 257 258 specular_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 259 specular_mix_node.location = (1473.2079, -382.7421) 260 specular_mix_node.blend_type = 'SCREEN' 261 specular_mix_node.inputs[0].default_value = mate.specular_intensity 262 263 normal_node = node_tree.nodes.new('ShaderNodeNormal') 264 normal_node.location = (912.1372, -590.8748) 265 266 rim_ramp_node = node_tree.nodes.new('ShaderNodeValToRGB') 267 rim_ramp_node.location = (1119.0664, -570.0284) 268 rim_ramp_node.color_ramp.elements[0].color = list(rimcolor[:]) + [1.0] 269 rim_ramp_node.color_ramp.elements[0].position = rimshift 270 rim_ramp_node.color_ramp.elements[1].color = (0, 0, 0, 1) 271 rim_ramp_node.color_ramp.elements[1].position = (rimshift) + ((1.0 - (rimpower * 0.03333)) * 0.5) 272 273 rim_power_node = node_tree.nodes.new('ShaderNodeHueSaturation') 274 rim_power_node.location = (1426.6332, -575.6142) 275 # rim_power_node.inputs[2].default_value = rimpower * 0.1 276 277 rim_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 278 rim_mix_node.location = (1724.7024, -451.9624) 279 rim_mix_node.blend_type = 'ADD' 280 281 out_node = node_tree.nodes.new('ShaderNodeOutput') 282 out_node.location = (1957.4023, -480.5365) 283 284 node_tree.links.new(shadow_mix_node.inputs[1], mate_node.outputs[0]) 285 node_tree.links.new(shadow_rate_node.inputs[0], shade_mate_node.outputs[3]) 286 node_tree.links.new(invert_node.inputs[1], shadow_rate_node.outputs[0]) 287 node_tree.links.new(shadow_mix_node.inputs[0], invert_node.outputs[0]) 288 node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3]) 289 node_tree.links.new(shadow_texture_node.inputs[0], geometry_node.outputs[4]) 290 node_tree.links.new(shadow_mix_node.inputs[2], shadow_texture_node.outputs[1]) 291 node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3]) 292 node_tree.links.new(toon_mix_node.inputs[1], shadow_mix_node.outputs[0]) 293 node_tree.links.new(toon_mix_node.inputs[2], toon_node.outputs[0]) 294 node_tree.links.new(specular_mix_node.inputs[1], toon_mix_node.outputs[0]) 295 node_tree.links.new(specular_mix_node.inputs[2], shade_mate_node.outputs[4]) 296 node_tree.links.new(normal_node.inputs[0], mate_node.outputs[2]) 297 node_tree.links.new(rim_ramp_node.inputs[0], normal_node.outputs[1]) 298 node_tree.links.new(rim_power_node.inputs[4], rim_ramp_node.outputs[0]) 299 node_tree.links.new(rim_mix_node.inputs[2], rim_power_node.outputs[0]) 300 node_tree.links.new(rim_mix_node.inputs[0], shadow_rate_node.outputs[0]) 301 node_tree.links.new(rim_mix_node.inputs[1], specular_mix_node.outputs[0]) 302 node_tree.links.new(out_node.inputs[0], rim_mix_node.outputs[0]) 303 node_tree.links.new(out_node.inputs[1], mate_node.outputs[1]) 304 305 for node in node_tree.nodes[:]: 306 compat.set_select(node, False) 307 node_tree.nodes.active = mate_node 308 node_tree.nodes.active.select = True 309 310 else: 311 mate.use_nodes = False 312 mate.use_shadeless = False 313 314# CM3D2用マテリアルを設定に合わせて装飾 315def decorate_material(mate, enable=True, me=None, mate_index=-1): 316 if not enable or 'shader1' not in mate: 317 return 318 if compat.IS_LEGACY: 319 return decorate_material_old(mate, enable=enable, me=me, mate_index=mate_index) 320 321 # luvoid : set properties of the mate 322 shader = mate['shader1'] 323 mate.preview_render_type = 'FLAT' 324 mate.blend_method = 'BLEND' if 'Trans' in shader else 'OPAQUE' 325 mate.use_backface_culling = 'Outline' not in shader 326 327 # luvoid : create cm3d2 shader node group and material output node 328 cmnode = None 329 if not compat.IS_LEGACY: 330 mate.use_nodes = True 331 #cm3d2_data.clear_nodes(mate.node_tree.nodes) 332 cmtree = bpy.data.node_groups.get('CM3D2 Shader') 333 if not cmtree: 334 blend_path = os.path.join(os.path.dirname(__file__), "append_data.blend") 335 with bpy.data.libraries.load(blend_path) as (data_from, data_to): 336 data_to.node_groups = ['CM3D2 Shader'] 337 cmtree = data_to.node_groups[0] 338 cmnode = mate.node_tree.nodes.new('ShaderNodeGroup') 339 cmnode.node_tree = cmtree 340 matout = mate.node_tree.nodes.new('ShaderNodeOutputMaterial') 341 matout.location = (300,0) 342 mate.node_tree.links.new(matout.inputs.get('Surface'), cmnode.outputs.get('Surface')) 343 344 for key, node in mate.node_tree.nodes.items(): 345 if not key.startswith('_'): 346 continue 347 if type(node) == bpy.types.ShaderNodeTexImage: 348 # luvoid : attatch tex node to cmnode sockets 349 socket = cmnode.inputs.get(key+" Color") 350 if socket: 351 mate.node_tree.links.new(socket, node.outputs.get('Color')) 352 socket = cmnode.inputs.get(key+" Alpha") 353 if socket: 354 mate.node_tree.links.new(socket, node.outputs.get('Alpha')) 355 else: 356 # luvoid : attatch color/float node to cmnode socket 357 input_socket = cmnode.inputs.get(key) 358 output_socket = node.outputs.get('Color') or node.outputs.get('Value') 359 if input_socket and output_socket: 360 mate.node_tree.links.new(input_socket, output_socket) 361 362 363 364 365# 画像のおおよその平均色を取得 366def get_image_average_color(img, sample_count=10): 367 if not len(img.pixels): 368 return mathutils.Color([0, 0, 0]) 369 370 pixel_count = img.size[0] * img.size[1] 371 channels = img.channels 372 373 max_s = 0.0 374 max_s_color, average_color = mathutils.Color([0, 0, 0]), mathutils.Color([0, 0, 0]) 375 seek_interval = pixel_count / sample_count 376 for sample_index in range(sample_count): 377 378 index = int(seek_interval * sample_index) * channels 379 color = mathutils.Color(img.pixels[index: index + 3]) 380 average_color += color 381 if max_s < color.s: 382 max_s_color, max_s = color, color.s 383 384 average_color /= sample_count 385 output_color = (average_color + max_s_color) / 2 386 output_color.s *= 1.5 387 return max_s_color 388 389 390# 画像のおおよその平均色を取得 (UV版) 391def get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10): 392 if not len(img.pixels): return mathutils.Color([0, 0, 0]) 393 394 img_width, img_height, img_channel = img.size[0], img.size[1], img.channels 395 396 bm = bmesh.new() 397 bm.from_mesh(me) 398 uv_lay = bm.loops.layers.uv.active 399 uvs = [l[uv_lay].uv[:] for f in bm.faces if f.material_index == mate_index for l in f.loops] 400 bm.free() 401 402 if len(uvs) <= sample_count: 403 return get_image_average_color(img) 404 405 average_color = mathutils.Color([0, 0, 0]) 406 max_s = 0.0 407 max_s_color = mathutils.Color([0, 0, 0]) 408 seek_interval = len(uvs) / sample_count 409 for sample_index in range(sample_count): 410 411 uv_index = int(seek_interval * sample_index) 412 x, y = uvs[uv_index] 413 414 x = math.modf(x)[0] 415 if x < 0.0: 416 x += 1.0 417 y = math.modf(y)[0] 418 if y < 0.0: 419 y += 1.0 420 421 x, y = int(x * img_width), int(y * img_height) 422 423 pixel_index = ((y * img_width) + x) * img_channel 424 color = mathutils.Color(img.pixels[pixel_index: pixel_index + 3]) 425 426 average_color += color 427 if max_s < color.s: 428 max_s_color, max_s = color, color.s 429 430 average_color /= sample_count 431 output_color = (average_color + max_s_color) / 2 432 output_color.s *= 1.5 433 return output_color 434 435 436def get_cm3d2_dir(): 437 try: 438 import winreg 439 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムメイド3D2') as key: 440 return winreg.QueryValueEx(key, 'InstallPath')[0] 441 except: 442 return None 443 444 445def get_com3d2_dir(): 446 try: 447 import winreg 448 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\KISS\カスタムオーダーメイド3D2') as key: 449 return winreg.QueryValueEx(key, 'InstallPath')[0] 450 except: 451 return None 452 453 454# CM3D2のインストールフォルダを取得+α 455def default_cm3d2_dir(base_dir, file_name, new_ext): 456 if not base_dir: 457 prefs = preferences() 458 if prefs.cm3d2_path: 459 base_dir = os.path.join(prefs.cm3d2_path, "GameData", "*." + new_ext) 460 else: 461 base_dir = get_cm3d2_dir() 462 if base_dir is None: 463 base_dir = get_com3d2_dir() 464 465 if base_dir: 466 prefs.cm3d2_path = base_dir 467 base_dir = os.path.join(base_dir, "GameData", "*." + new_ext) 468 469 if base_dir is None: 470 base_dir = "." 471 472 if file_name: 473 base_dir = os.path.join(os.path.split(base_dir)[0], file_name) 474 base_dir = os.path.splitext(base_dir)[0] + "." + new_ext 475 return base_dir 476 477 478# 一時ファイル書き込みと自動バックアップを行うファイルオブジェクトを返す 479def open_temporary(filepath, mode, is_backup=False): 480 backup_ext = preferences().backup_ext 481 if is_backup and backup_ext: 482 backup_filepath = filepath + '.' + backup_ext 483 else: 484 backup_filepath = None 485 return fileutil.TemporaryFileWriter(filepath, mode, backup_filepath=backup_filepath) 486 487 488# ファイルを上書きするならバックアップ処理 489def file_backup(filepath, enable=True): 490 backup_ext = preferences().backup_ext 491 if enable and backup_ext and os.path.exists(filepath): 492 shutil.copyfile(filepath, filepath + "." + backup_ext) 493 494 495# サブフォルダを再帰的に検索してリスト化 496def find_tex_all_files(dir): 497 for root, dirs, files in os.walk(dir): 498 for f in files: 499 ext = os.path.splitext(f)[1].lower() 500 if ext == ".tex" or ext == ".png": 501 yield os.path.join(root, f) 502 503 504# テクスチャ置き場のパスのリストを返す 505def get_default_tex_paths(): 506 prefs = preferences() 507 default_paths = [prefs.default_tex_path0, prefs.default_tex_path1, prefs.default_tex_path2, prefs.default_tex_path3] 508 if not any(default_paths): 509 target_dirs = [] 510 cm3d2_dir = prefs.cm3d2_path 511 if not cm3d2_dir: 512 cm3d2_dir = get_cm3d2_dir() 513 514 if cm3d2_dir: 515 target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture")) 516 target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture2")) 517 target_dirs.append(os.path.join(cm3d2_dir, "Sybaris", "GameData")) 518 target_dirs.append(os.path.join(cm3d2_dir, "Mod")) 519 520 # com3d2_dir = prefs.com3d2_path 521 # if not com3d2_dir: 522 # com3d2_dir = get_cm3d2_dir() 523 # if com3d2_dir: 524 # target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts")) 525 # target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts2")) 526 # target_dirs.append(os.path.join(com3d2_dir, "MOD")) 527 528 tex_dirs = [path for path in target_dirs if os.path.isdir(path)] 529 530 for index, path in enumerate(tex_dirs): 531 setattr(prefs, 'default_tex_path' + str(index), path) 532 else: 533 tex_dirs = [getattr(prefs, 'default_tex_path' + str(i)) for i in range(4) if getattr(prefs, 'default_tex_path' + str(i))] 534 return tex_dirs 535 536 537# テクスチャ置き場の全ファイルを返す 538def get_tex_storage_files(): 539 files = [] 540 tex_dirs = get_default_tex_paths() 541 for tex_dir in tex_dirs: 542 tex_dir = bpy.path.abspath(tex_dir) 543 files.extend(find_tex_all_files(tex_dir)) 544 return files 545 546 547def get_texpath_dict(reload=False): 548 if reload or len(texpath_dict) == 0: 549 texpath_dict.clear() 550 tex_dirs = get_default_tex_paths() 551 for tex_dir in tex_dirs: 552 for path in find_tex_all_files(tex_dir): 553 path = bpy.path.abspath(path) 554 file_name = os.path.basename(path).lower() 555 # 先に見つけたファイルを優先 556 if file_name not in texpath_dict: 557 texpath_dict[file_name] = path 558 return texpath_dict 559 560 561def reload_png(img, texpath_dict, png_name): 562 png_path = texpath_dict.get(png_name) 563 if png_path: 564 img.filepath = png_path 565 img.reload() 566 return True 567 return False 568 569 570def replace_cm3d2_tex(img, texpath_dict: dict=None, reload_path: bool=True) -> bool: 571 """replace tex file. 572 pngファイルを先に走査し、見つからなければtexファイルを探す. 573 texはpngに展開して読み込みを行う. 574 reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、 575 再度検索を行う. 576 577 Parameters: 578 img (Image): イメージオブジェクト 579 texpath_dict (dict): テクスチャパスのdict (キャッシュ) 580 reload_path (bool): 見つからない場合にキャッシュを再読込するか 581 582 Returns: 583 bool: tex load successful 584 """ 585 if texpath_dict is None: 586 texpath_dict = get_texpath_dict() 587 588 if __replace_cm3d2_tex(img, texpath_dict): 589 return True 590 if reload_path: 591 texpath_dict = get_texpath_dict(True) 592 return __replace_cm3d2_tex(img, texpath_dict) 593 return False 594 595 596def __replace_cm3d2_tex(img, texpath_dict: dict) -> bool: 597 source_name = remove_serial_number(img.name).lower() 598 599 source_png_name = source_name + ".png" 600 if reload_png(img, texpath_dict, source_png_name): 601 return True 602 603 source_tex_name = source_name + ".tex" 604 tex_path = texpath_dict.get(source_tex_name) 605 try: 606 if tex_path is None: 607 return False 608 tex_data = load_cm3d2tex(tex_path) 609 if tex_data is None: 610 return False 611 612 png_path = tex_path[:-4] + ".png" 613 with open(png_path, 'wb') as png_file: 614 png_file.write(tex_data[-1]) 615 img.filepath = png_path 616 img.reload() 617 return True 618 except: 619 pass 620 return False 621 622 623# texファイルの読み込み 624def load_cm3d2tex(path, skip_data=False): 625 626 with open(path, 'rb') as file: 627 header_ext = read_str(file) 628 if header_ext != 'CM3D2_TEX': 629 return None 630 version = struct.unpack('<i', file.read(4))[0] 631 read_str(file) 632 633 # default value 634 tex_format = 5 635 uv_rects = None 636 data = None 637 if version >= 1010: 638 if version >= 1011: 639 num_rect = struct.unpack('<i', file.read(4))[0] 640 uv_rects = [] 641 for i in range(num_rect): 642 # x, y, w, h 643 uv_rects.append(struct.unpack('<4f', file.read(4 * 4))) 644 width = struct.unpack('<i', file.read(4))[0] 645 height = struct.unpack('<i', file.read(4))[0] 646 tex_format = struct.unpack('<i', file.read(4))[0] 647 # if tex_format == 10 or tex_format == 12: return None 648 if not skip_data: 649 png_size = struct.unpack('<i', file.read(4))[0] 650 data = file.read(png_size) 651 return version, tex_format, uv_rects, data 652 653 654def create_tex(context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1): 655 if isinstance(context, bpy.types.Context): 656 context = context.copy() 657 658 if compat.IS_LEGACY: 659 slot = mate.texture_slots.create(slot_index) 660 tex = context['blend_data'].textures.new(node_name, 'IMAGE') 661 slot.texture = tex 662 663 if tex_name: 664 slot.offset[0] = tex_map_data[0] 665 slot.offset[1] = tex_map_data[1] 666 slot.scale[0] = tex_map_data[2] 667 slot.scale[1] = tex_map_data[3] 668 669 if os.path.exists(filepath): 670 img = bpy.data.images.load(filepath) 671 img.name = tex_name 672 else: 673 img = context['blend_data'].images.new(tex_name, 128, 128) 674 img.filepath = filepath 675 img['cm3d2_path'] = cm3d2path 676 img.source = 'FILE' 677 tex.image = img 678 679 if replace_tex: 680 replaced = replace_cm3d2_tex(tex.image, reload_path=False) 681 if replaced and node_name == '_MainTex': 682 ob = context['active_object'] 683 me = ob.data 684 for face in me.polygons: 685 if face.material_index == ob.active_material_index: 686 me.uv_textures.active.data[face.index].image = tex.image 687 688 else: 689 # if mate.use_nodes is False: 690 # mate.use_nodes = True 691 nodes = mate.node_tree.nodes 692 tex = nodes.get(node_name) 693 if tex is None: 694 tex = mate.node_tree.nodes.new(type='ShaderNodeTexImage') 695 tex.name = tex.label = node_name 696 tex.show_texture = True 697 698 if tex_name: 699 if tex.image is None: 700 if os.path.exists(filepath): 701 img = bpy.data.images.load(filepath) 702 img.name = tex_name 703 else: 704 img = context['blend_data'].images.new(tex_name, 128, 128) 705 img.filepath = filepath 706 img.source = 'FILE' 707 tex.image = img 708 img['cm3d2_path'] = cm3d2path 709 else: 710 img = tex.image 711 path = img.get('cm3d2_path') 712 if path != cm3d2path: 713 img['cm3d2_path'] = cm3d2path 714 img.filepath = filepath 715 716 tex_map = tex.texture_mapping 717 tex_map.translation[0] = tex_map_data[0] 718 tex_map.translation[1] = tex_map_data[1] 719 tex_map.scale[0] = tex_map_data[2] 720 tex_map.scale[1] = tex_map_data[3] 721 722 # tex.color = tex_data['color'][:3] 723 # tex.outputs['Color'].default_value = tex_data['color'][:] 724 # tex.outputs['ALpha'].default_value = tex_data['color'][3] 725 726 # tex探し 727 if replace_tex: 728 replaced = replace_cm3d2_tex(tex.image, reload_path=False) 729 # TODO 2.8での実施方法を調査. shader editorで十分? 730 731 return tex 732 733 734def create_col(context, mate, node_name, color, slot_index=-1): 735 if isinstance(context, bpy.types.Context): 736 context = context.copy() 737 738 if compat.IS_LEGACY: 739 if slot_index >= 0: 740 mate.use_textures[slot_index] = False 741 node = mate.texture_slots.create(slot_index) 742 node.color = color[:3] 743 node.diffuse_color_factor = color[3] 744 node.use_rgb_to_intensity = True 745 tex = context['blend_data'].textures.new(node_name, 'BLEND') 746 node.texture = tex 747 node.use = False 748 else: 749 node = mate.node_tree.nodes.get(node_name) 750 if node is None: 751 node = mate.node_tree.nodes.new(type='ShaderNodeRGB') 752 node.name = node.label = node_name 753 node.outputs[0].default_value = color 754 755 return node 756 757 758def create_float(context, mate, node_name, value, slot_index=-1): 759 if isinstance(context, bpy.types.Context): 760 context = context.copy() 761 762 if compat.IS_LEGACY: 763 if slot_index >= 0: 764 mate.use_textures[slot_index] = False 765 node = mate.texture_slots.create(slot_index) 766 node.diffuse_color_factor = value 767 node.use_rgb_to_intensity = False 768 tex = context['blend_data'].textures.new(node_name, 'BLEND') 769 node.texture = tex 770 node.use = False 771 else: 772 node = mate.node_tree.nodes.get(node_name) 773 if node is None: 774 node = mate.node_tree.nodes.new(type='ShaderNodeValue') 775 node.name = node.label = node_name 776 node.outputs[0].default_value = value 777 778 return node 779 780 781def setup_material(mate): 782 if mate: 783 if 'CM3D2 Texture Expand' not in mate: 784 mate['CM3D2 Texture Expand'] = True 785 786 if not compat.IS_LEGACY: 787 mate.use_nodes = True 788 789 790def setup_image_name(img): 791 """イメージの名前から拡張子を除外する""" 792 # consider case with serial number. ex) sample.png.001 793 img.name = re_png.sub(r'\1', img.name) 794 795 796def get_tex_cm3d2path(filepath): 797 return BASE_PATH_TEX + os.path.basename(filepath) 798 799 800def to_cm3d2path(path): 801 path = path.replace('\\', '/') 802 path = re_prefix.sub('', path) 803 if not re_path_prefix.search(path): 804 path = get_tex_cm3d2path(path) 805 return path 806 807 808# col f タイプの設定値を値に合わせて着色 809def set_texture_color(slot): 810 if not slot or not slot.texture or slot.use: 811 return 812 813 slot_type = 'col' if slot.use_rgb_to_intensity else 'f' 814 tex = slot.texture 815 base_name = remove_serial_number(tex.name) 816 tex.type = 'BLEND' 817 818 if hasattr(tex, 'progression'): 819 tex.progression = 'DIAGONAL' 820 tex.use_color_ramp = True 821 tex.use_preview_alpha = True 822 elements = tex.color_ramp.elements 823 824 element_count = 4 825 if element_count < len(elements): 826 for i in range(len(elements) - element_count): 827 elements.remove(elements[-1]) 828 elif len(elements) < element_count: 829 for i in range(element_count - len(elements)): 830 elements.new(1.0) 831 832 elements[0].position, elements[1].position, elements[2].position, elements[3].position = 0.2, 0.21, 0.25, 0.26 833 834 if slot_type == 'col': 835 elements[0].color = [0.2, 1, 0.2, 1] 836 elements[-1].color = slot.color[:] + (slot.diffuse_color_factor, ) 837 if 0.3 < mathutils.Color(slot.color[:3]).v: 838 elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1] 839 else: 840 elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1] 841 842 elif slot_type == 'f': 843 elements[0].color = [0.2, 0.2, 1, 1] 844 multi = 1.0 845 if base_name == '_OutlineWidth': 846 multi = 200 847 elif base_name == '_RimPower': 848 multi = 1.0 / 30.0 849 value = slot.diffuse_color_factor * multi 850 elements[-1].color = [value, value, value, 1] 851 if 0.3 < value: 852 elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1] 853 else: 854 elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1] 855 856 857# 必要なエリアタイプを設定を変更してでも取得 858def get_request_area(context, request_type, except_types=None): 859 if except_types is None: 860 except_types = ['VIEW_3D', 'PROPERTIES', 'INFO', compat.pref_type()] 861 862 request_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type == request_type] 863 candidate_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type not in except_types] 864 865 return_areas = request_areas[:] if len(request_areas) else candidate_areas 866 if not len(return_areas): 867 return None 868 869 return_areas.sort(key=lambda i: i[1]) 870 return_area = return_areas[-1][0] 871 return_area.type = request_type 872 return return_area 873 874 875# 複数のデータを完全に削除 876def remove_data(target_data): 877 try: 878 target_data = target_data[:] 879 except: 880 target_data = [target_data] 881 882 if compat.IS_LEGACY: 883 for data in target_data: 884 if data.__class__.__name__ == 'Object': 885 if data.name in bpy.context.scene.objects: 886 bpy.context.scene.objects.unlink(data) 887 else: 888 for data in target_data: 889 if data.__class__.__name__ == 'Object': 890 if data.name in bpy.context.scene.collection.objects: 891 bpy.context.scene.collection.objects.unlink(data) 892 893 # https://developer.blender.org/T49837 894 # によると、xxx.remove(data, do_unlink=True)で十分 895 # 896 # for data in target_data: 897 # users = getattr(data, 'users') 898 # if users and 'user_clear' in dir(data): 899 # data.user_clear() 900 901 for data in target_data: 902 for data_str in dir(bpy.data): 903 if not data_str.endswith('s'): 904 continue 905 try: 906 if data.__class__.__name__ == eval('bpy.data.%s[0].__class__.__name__' % data_str): 907 exec('bpy.data.%s.remove(data, do_unlink=True)' % data_str) 908 break 909 except: 910 pass 911 912 913# オブジェクトのマテリアルを削除/復元するクラス 914class material_restore: 915 def __init__(self, ob): 916 override = bpy.context.copy() 917 override['object'] = ob 918 self.object = ob 919 920 self.slots = [slot.material if slot.material else None for slot in ob.material_slots] 921 922 self.mesh_data = [] 923 for index, slot in enumerate(ob.material_slots): 924 mesh_datum = [] 925 for face in ob.data.polygons: 926 if face.material_index == index: 927 mesh_datum.append(face.index) 928 self.mesh_data.append(mesh_datum) 929 930 for slot in ob.material_slots[:]: 931 bpy.ops.object.material_slot_remove(override) 932 933 def restore(self): 934 override = bpy.context.copy() 935 override['object'] = self.object 936 937 for slot in self.object.material_slots[:]: 938 bpy.ops.object.material_slot_remove(override) 939 940 for index, mate in enumerate(self.slots): 941 bpy.ops.object.material_slot_add(override) 942 slot = self.object.material_slots[index] 943 if slot: 944 slot.material = mate 945 for face_index in self.mesh_data[index]: 946 self.object.data.polygons[face_index].material_index = index 947 948 949# 現在のレイヤー内のオブジェクトをレンダリングしなくする/戻す 950class hide_render_restore: 951 def __init__(self, render_objects=[]): 952 try: 953 render_objects = render_objects[:] 954 except: 955 render_objects = [render_objects] 956 957 if not len(render_objects): 958 render_objects = bpy.context.selected_objects[:] 959 960 self.render_objects = render_objects[:] 961 self.render_object_names = [ob.name for ob in render_objects] 962 963 self.rendered_objects = [] 964 for ob in render_objects: 965 if ob.hide_render: 966 self.rendered_objects.append(ob) 967 ob.hide_render = False 968 969 self.hide_rendered_objects = [] 970 if compat.IS_LEGACY: 971 for ob in bpy.data.objects: 972 for layer_index, is_used in enumerate(bpy.context.scene.layers): 973 if not is_used: 974 continue 975 if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render: 976 self.hide_rendered_objects.append(ob) 977 ob.hide_render = True 978 break 979 else: 980 clct_children = bpy.context.scene.collection.children 981 for ob in bpy.data.objects: 982 if ob.name not in self.render_object_names and not ob.hide_render: 983 # ble-2.8ではlayerではなく、collectionからのリンクで判断 984 for clct in bpy.context.window.view_layer.layer_collection.children: 985 if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys(): 986 self.hide_rendered_objects.append(ob) 987 ob.hide_render = True 988 break 989 990 def restore(self): 991 for ob in self.rendered_objects: 992 ob.hide_render = True 993 for ob in self.hide_rendered_objects: 994 ob.hide_render = False 995 996 997# 指定エリアに変数をセット 998def set_area_space_attr(area, attr_name, value): 999 if not area: 1000 return 1001 for space in area.spaces: 1002 if space.type == area.type: 1003 space.__setattr__(attr_name, value) 1004 break 1005 1006 1007# スムーズなグラフを返す1 1008def in_out_quad_blend(f): 1009 if f <= 0.5: 1010 return 2.0 * math.sqrt(f) 1011 f -= 0.5 1012 return 2.0 * f * (1.0 - f) + 0.5 1013 1014 1015# スムーズなグラフを返す2 1016def bezier_blend(f): 1017 return math.sqrt(f) * (3.0 - 2.0 * f) 1018 1019 1020# 三角関数でスムーズなグラフを返す 1021def trigonometric_smooth(x): 1022 return math.sin((x - 0.5) * math.pi) * 0.5 + 0.5 1023 1024 1025# エクスポート例外クラス 1026class CM3D2ExportException(Exception): 1027 pass 1028 1029class CM3D2ImportException(Exception): 1030 pass 1031 1032 1033# ノード取得クラス 1034class NodeHandler(): 1035 node_name = bpy.props.StringProperty(name='NodeName') 1036 1037 def get_node(self, context): 1038 mate = context.material 1039 if mate and mate.use_nodes: 1040 return mate.node_tree.nodes.get(self.node_name) 1041 1042 # if node is None: 1043 # # 見つからない場合は、シリアル番号付きのノードを探す 1044 # prefix = self.node_name + '.' 1045 # for n in nodes: 1046 # if n.name.startwith(prefix): 1047 # node = n 1048 # break 1049 1050 return None 1051 1052#@compat.BlRegister() 1053class CNV_UL_generic_selector(bpy.types.UIList): 1054 bl_label = "CNV_UL_generic_selector" 1055 bl_options = {'DEFAULT_CLOSED'} 1056 bl_region_type = 'WINDOW' 1057 bl_space_type = 'PROPERTIES' 1058 1059 # Constants (flags) 1060 # Be careful not to shadow FILTER_ITEM! 1061 #bitflag_soft_filter = 1073741824 >> 0 1062 bitflag_soft_filter = 1073741824 >> 3 1063 1064 bitflag_forced_value = 1073741824 >> 10 1065 bitflag_forced_true = 1073741824 >> 11 1066 bitflag_forced_false = 1073741824 >> 12 1067 1068 1069 cached_values = {} 1070 expanded_layout = False 1071 1072 # Custom properties, saved with .blend file. 1073 use_filter_name_reverse = bpy.props.BoolProperty( 1074 name="Reverse Name", 1075 default=False, 1076 options=set(), 1077 description="Reverse name filtering", 1078 ) 1079 #use_filter_deform = bpy.props.BoolProperty( 1080 # name="Only Deform", 1081 # default=True, 1082 # options=set(), 1083 # description="Only show deforming vertex groups", 1084 #) 1085 #use_filter_deform_reverse = bpy.props.BoolProperty( 1086 # name="Other", 1087 # default=False, 1088 # options=set(), 1089 # description="Only show non-deforming vertex groups", 1090 #) 1091 #use_filter_empty = bpy.props.BoolProperty( 1092 # name="Filter Empty", 1093 # default=False, 1094 # options=set(), 1095 # description="Whether to filter empty vertex groups", 1096 #) 1097 #use_filter_empty_reverse = bpy.props.BoolProperty( 1098 # name="Reverse Empty", 1099 # default=False, 1100 # options=set(), 1101 # description="Reverse empty filtering", 1102 #) 1103 1104 # This allows us to have mutually exclusive options, which are also all disable-able! 1105 def _gen_order_update(name1, name2): 1106 def _u(self, ctxt): 1107 if (getattr(self, name1)): 1108 setattr(self, name2, False) 1109 return _u 1110 use_order_name = bpy.props.BoolProperty( 1111 name="Name", default=False, options=set(), 1112 description="Sort groups by their name (case-insensitive)", 1113 update=_gen_order_update("use_order_name", "use_order_importance"), 1114 ) 1115 use_filter_orderby_invert = bpy.props.BoolProperty( 1116 name="Order by Invert", 1117 default=False, 1118 options=set(), 1119 description="Invert the sort by order" 1120 ) 1121 #use_order_importance = bpy.props.BoolProperty( 1122 # name="Importance", 1123 # default=False, 1124 # options=set(), 1125 # description="Sort groups by their average weight in the mesh", 1126 # update=_gen_order_update("use_order_importance", "use_order_name"), 1127 #) 1128 1129 # Usual draw item function. 1130 def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag): 1131 # Just in case, we do not use it here! 1132 self.use_filter_invert = False 1133 1134 # assert(isinstance(item, bpy.types.VertexGroup) 1135 #vgroup = getattr(data, 'matched_vgroups')[item.index] 1136 if self.layout_type in {'DEFAULT', 'COMPACT'}: 1137 # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag 1138 # parameter, which contains exactly what filter_items set in its filter list for this item! 1139 # In this case, we show empty groups grayed out. 1140 cached_value = self.cached_values.get(item.name, None) 1141 if (cached_value != None) and (cached_value != item.value): 1142 item.preferred = item.value 1143 1144 force_values = flt_flag & self.bitflag_forced_value 1145 print("GET force_values =", force_values) 1146 if force_values: 1147 print("FORCE VALUES") 1148 if flt_flag & self.bitflag_forced_true: 1149 item.value = True 1150 elif flt_flag & self.bitflag_forced_false: 1151 item.value = False 1152 else: 1153 item.value = item.preferred 1154 1155 self.cached_values[item.name] = item.value 1156 1157 if flt_flag & self.bitflag_soft_filter: 1158 row = layout.row() 1159 row.enabled = False 1160 #row.alignment = 'LEFT' 1161 row.prop(item, "value", text=item.name, icon=item.icon) 1162 else: 1163 layout.prop(item, "value", text=item.name, icon=item.icon) 1164 1165 #layout.prop(item, "value", text=item.name, icon=item.icon) 1166 icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF' 1167 layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False) 1168 1169 elif self.layout_type in {'GRID'}: 1170 layout.alignment = 'CENTER' 1171 if flt_flag & self.VGROUP_EMPTY: 1172 layout.enabled = False 1173 layout.label(text="", icon_value=icon) 1174 1175 def draw_filter(self, context, layout): 1176 # Nothing much to say here, it's usual UI code... 1177 row = layout.row() 1178 if not self.expanded_layout: 1179 layout.active = True 1180 layout.enabled = True 1181 row.active = True 1182 row.enabled = True 1183 self.expanded_layout = True 1184 1185 subrow = row.row(align=True) 1186 subrow.prop(self, "filter_name", text="") 1187 icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN' 1188 subrow.prop(self, "use_filter_name_reverse", text="", icon=icon) 1189 1190 #subrow = row.row(align=True) 1191 #subrow.prop(self, "use_filter_deform", toggle=True) 1192 #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN' 1193 #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon) 1194 1195 #subrow = row.row(align=True) 1196 #subrow.prop(self, "use_filter_empty", toggle=True) 1197 #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN' 1198 #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon) 1199 1200 row = layout.row(align=True) 1201 row.label(text="Order by:") 1202 row.prop(self, "use_order_name", toggle=True) 1203 #row.prop(self, "use_order_importance", toggle=True) 1204 icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN' 1205 row.prop(self, "use_filter_orderby_invert", text="", icon=icon) 1206 1207 def filter_items(self, context, data, propname): 1208 # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists: 1209 # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the 1210 # matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the 1211 # first one to mark VGROUP_EMPTY. 1212 # * The second one is for reordering, it must return a list containing the new indices of the items (which 1213 # gives us a mapping org_idx -> new_idx). 1214 # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info). 1215 # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than 1216 # returning full lists doing nothing!). 1217 items = getattr(data, propname) 1218 1219 #if self.armature == None: 1220 # target_ob, source_ob = common.get_target_and_source_ob(context) 1221 # armature_ob = target_ob.find_armature() or source_ob.find_armature() 1222 # self.armature = armature_ob and armature_ob.data or False 1223 # 1224 #if not self.local_bone_names: 1225 # target_ob, source_ob = common.get_target_and_source_ob(context) 1226 # bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None 1227 # if bone_data_ob: 1228 # local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:")) 1229 # self.local_bone_names = [ bone['name'] for bone in local_bone_data ] 1230 1231 if not self.cached_values: 1232 self.cached_values = { item.name: item.value for item in items } 1233 #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0] for item in items ] 1234 helper_funcs = bpy.types.UI_UL_list 1235 1236 # Default return values. 1237 flt_flags = [] 1238 flt_neworder = [] 1239 1240 # Pre-compute of vgroups data, CPU-intensive. :/ 1241 #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups) 1242 1243 # Filtering by name 1244 if self.filter_name: 1245 flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name", 1246 reverse=self.use_filter_name_reverse) 1247 if not flt_flags: 1248 flt_flags = [self.bitflag_filter_item] * len(items) 1249 1250 #for idx, vg in enumerate(items): 1251 # # Filter by deform. 1252 # if self.use_filter_deform: 1253 # flt_flags[idx] |= self.VGROUP_DEFORM 1254 # if self.use_filter_deform: 1255 # if self.armature and self.armature.get(vg.name): 1256 # if not self.use_filter_deform_reverse: 1257 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1258 # elif bone_data_ob and (vg.name in self.local_bone_names): 1259 # if not self.use_filter_deform_reverse: 1260 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1261 # elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names): 1262 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1263 # else: 1264 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1265 # 1266 # # Filter by emptiness. 1267 # #if vgroups_empty[vg.index][0]: 1268 # # flt_flags[idx] |= self.VGROUP_EMPTY 1269 # # if self.use_filter_empty and self.use_filter_empty_reverse: 1270 # # flt_flags[idx] &= ~self.bitflag_filter_item 1271 # #elif self.use_filter_empty and not self.use_filter_empty_reverse: 1272 # # flt_flags[idx] &= ~self.bitflag_filter_item 1273 1274 # Reorder by name or average weight. 1275 if self.use_order_name: 1276 flt_neworder = helper_funcs.sort_items_by_name(items, "name") 1277 #elif self.use_order_importance: 1278 # _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)] 1279 # flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True) 1280 1281 return flt_flags, flt_neworder 1282 1283 1284 1285@compat.BlRegister() 1286class CNV_SelectorItem(bpy.types.PropertyGroup): 1287 bl_label = "CNV_SelectorItem" 1288 bl_region_type = 'WINDOW' 1289 bl_space_type = 'PROPERTIES' 1290 1291 name = bpy.props.StringProperty (name="Name" , default="Unknown") 1292 value = bpy.props.BoolProperty (name="Value" , default=True ) 1293 index = bpy.props.IntProperty (name="Index" , default=-1 ) 1294 preferred = bpy.props.BoolProperty (name="Prefered", default=True ) 1295 icon = bpy.props.StringProperty (name="Icon" , default='NONE' ) 1296 1297 filter0 = bpy.props.BoolProperty (name="Filter 0", default=False ) 1298 filter1 = bpy.props.BoolProperty (name="Filter 1", default=False ) 1299 filter2 = bpy.props.BoolProperty (name="Filter 2", default=False ) 1300 filter3 = bpy.props.BoolProperty (name="Filter 3", default=False ) 1301 1302 1303 1304 1305 1306# luvoid : for loop helper returns values with matching keys 1307def values_of_matched_keys(dict1, dict2): 1308 value_list = [] 1309 items1 = dict1.items() 1310 items2 = dict2.items() 1311 if len(items1) <= len(items2): 1312 items1.reverse() 1313 for k1, v1 in items1: 1314 for i in range(len(items2)-1, 0-1, -1): 1315 k2, v2 = items2[i] 1316 if k1 == k2: 1317 value_list.append((v1,v2)) 1318 del items2[i] 1319 else: 1320 items2.reverse() 1321 for k2, v2 in items2: 1322 for i in range(len(items1)-1, 0-1, -1): 1323 k1, v1 = items1[i] 1324 if k1 == k2: 1325 value_list.append((v1,v2)) 1326 del items1[i] 1327 1328 value_list.reverse() 1329 return value_list 1330 1331 1332# luvoid : helper to easily get source and target objects 1333def get_target_and_source_ob(context, copyTarget=False, copySource=False): 1334 target_ob = None 1335 source_ob = None 1336 target_original_ob = None 1337 source_original_ob = None 1338 1339 target_original_ob = context.object 1340 if copyTarget: 1341 target_ob = target_original_ob.copy() 1342 target_ob.data = target_original_ob.data.copy() 1343 else: 1344 target_ob = target_original_ob 1345 1346 for ob in context.selected_objects: 1347 if ob != target_ob: 1348 source_original_ob = ob 1349 break 1350 1351 if copySource: 1352 source_ob = source_original_ob.copy() 1353 source_ob.data = source_original_ob.data.copy() 1354 else: 1355 source_ob = source_original_ob 1356 1357 if copyTarget: 1358 if copySource: 1359 return target_ob, source_ob, target_original_ob, source_original_ob 1360 else: 1361 return target_ob, source_ob, target_original_ob 1362 elif copySource: 1363 return target_ob, source_ob, source_original_ob 1364 else: 1365 return target_ob, source_ob 1366 1367 1368# luvoid 1369def is_descendant_of(bone, ancestor) -> bool: 1370 """Returns true if a bone is the descendant of the given ancestor""" 1371 while bone.parent: 1372 bone = bone.parent 1373 if bone.name == ancestor.name: 1374 return True 1375 return False
bl_info =
{'name': 'CM3D2 Converter', 'author': '@saidenka_cm3d2, @trzrz, @luvoid', 'version': ('luv', 2023, 6, '11a'), 'blender': (2, 80, 0), 'location': 'ファイル > インポート/エクスポート > CM3D2 Model (.model)', 'description': 'カスタムメイド3D2/カスタムオーダーメイド3D2専用ファイルのインポート/エクスポートを行います', 'warning': '', 'wiki_url': 'https://github.com/luvoid/Blender-CM3D2-Converter/blob/bl_28/translations/en_US/README.md', 'tracker_url': 'https://github.com/luvoid/Blender-CM3D2-Converter', 'category': 'Import-Export'}
ADDON_NAME =
'CM3D2 Converter'
BASE_PATH_TEX =
'Assets/texture/texture/'
BRANCH =
'bl_28'
URL_REPOS =
'https://github.com/luvoid/Blender-CM3D2-Converter/'
URL_ATOM =
'https://github.com/luvoid/Blender-CM3D2-Converter/commits/{branch}.atom'
URL_MODULE =
'https://github.com/luvoid/Blender-CM3D2-Converter/archive/{branch}.zip'
KISS_ICON =
None
PREFS =
None
preview_collections =
{'main': <ImagePreviewCollection id=0x7f6a190195d0[1], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>}
texpath_dict =
{}
re_png =
re.compile('\\.[Pp][Nn][Gg](\\.\\d{3})?$')
re_serial =
re.compile('(\\.\\d{3})$')
re_prefix =
re.compile('^[\\/\\.]*')
re_path_prefix =
re.compile('^assets/', re.IGNORECASE)
re_ext_png =
re.compile('\\.png$', re.IGNORECASE)
re_bone1 =
re.compile('([_ ])\\*([_ ].*)\\.([rRlL])$')
re_bone2 =
re.compile('([_ ])([rRlL])([_ ].*)$')
def
preferences():
def
kiss_icon():
def
remove_serial_number(name, enable=True):
def
has_serial_number(name):
def
line_trim(line, enable=True):
def
write_str(file, raw_str):
68def write_str(file, raw_str): 69 b_str = format(len(raw_str.encode('utf-8')), 'b') 70 for i in range(9): 71 if 7 < len(b_str): 72 file.write(struct.pack('<B', int("1" + b_str[-7:], 2))) 73 b_str = b_str[:-7] 74 else: 75 file.write(struct.pack('<B', int(b_str, 2))) 76 break 77 file.write(raw_str.encode('utf-8'))
def
pack_str(buffer, raw_str):
79def pack_str(buffer, raw_str): 80 b_str = format(len(raw_str.encode('utf-8')), 'b') 81 for i in range(9): 82 if 7 < len(b_str): 83 buffer = buffer + struct.pack('<B', int("1" + b_str[-7:], 2)) 84 b_str = b_str[:-7] 85 else: 86 buffer = buffer + struct.pack('<B', int(b_str, 2)) 87 break 88 buffer = buffer + raw_str.encode('utf-8') 89 return buffer
def
read_str(file, total_b=''):
def
encode_bone_name(name, enable=True):
def
decode_bone_name(name, enable=True):
def
decorate_material_old(mate, enable=True, me=None, mate_index=-1):
113def decorate_material_old(mate, enable=True, me=None, mate_index=-1): 114 if not compat.IS_LEGACY or not enable or 'shader1' not in mate: 115 return 116 117 shader = mate['shader1'] 118 if 'CM3D2/Man' == shader: 119 mate.use_shadeless = True 120 mate.diffuse_color = (0, 1, 1) 121 elif 'CM3D2/Mosaic' == shader: 122 mate.use_transparency = True 123 mate.transparency_method = 'RAYTRACE' 124 mate.alpha = 0.25 125 mate.raytrace_transparency.ior = 2 126 elif 'CM3D2_Debug/Debug_CM3D2_Normal2Color' == shader: 127 mate.use_tangent_shading = True 128 mate.diffuse_color = (0.5, 0.5, 1) 129 130 else: 131 if '/Toony_' in shader: 132 mate.diffuse_shader = 'TOON' 133 mate.diffuse_toon_smooth = 0.01 134 mate.diffuse_toon_size = 1.2 135 if 'Trans' in shader: 136 mate.use_transparency = True 137 mate.alpha = 0.0 138 mate.texture_slots[0].use_map_alpha = True 139 if 'Unlit/' in shader: 140 mate.emit = 0.5 141 if '_NoZ' in shader: 142 mate.offset_z = 9999 143 144 is_colored = False 145 is_textured = [False, False, False, False] 146 rimcolor, rimpower, rimshift = mathutils.Color((1, 1, 1)), 0.0, 0.0 147 for slot in mate.texture_slots: 148 if not slot or not slot.texture: 149 continue 150 151 tex = slot.texture 152 tex_name = remove_serial_number(tex.name) 153 slot.use_map_color_diffuse = False 154 155 if tex_name == '_MainTex': 156 slot.use_map_color_diffuse = True 157 img = getattr(tex, 'image') 158 if img and len(img.pixels): 159 if me: 160 color = mathutils.Color(get_image_average_color_uv(img, me, mate_index)[:3]) 161 else: 162 color = mathutils.Color(get_image_average_color(img)[:3]) 163 mate.diffuse_color = color 164 is_colored = True 165 166 elif tex_name == '_RimColor': 167 rimcolor = slot.color[:] 168 if not is_colored: 169 mate.diffuse_color = slot.color[:] 170 mate.diffuse_color.v += 0.5 171 172 elif tex_name == '_Shininess': 173 mate.specular_intensity = slot.diffuse_color_factor 174 175 elif tex_name == '_RimPower': 176 rimpower = slot.diffuse_color_factor 177 178 elif tex_name == '_RimShift': 179 rimshift = slot.diffuse_color_factor 180 181 for index, name in enumerate(['_MainTex', '_ToonRamp', '_ShadowTex', '_ShadowRateToon']): 182 if tex_name == name: 183 img = getattr(tex, 'image') 184 if img and len(tex.image.pixels): 185 is_textured[index] = tex 186 187 set_texture_color(slot) 188 189 # よりオリジナルに近く描画するノード作成 190 if all(is_textured): 191 mate.use_nodes = True 192 mate.use_shadeless = True 193 194 node_tree = mate.node_tree 195 for node in node_tree.nodes[:]: 196 node_tree.nodes.remove(node) 197 198 mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial') 199 mate_node.location = (0, 0) 200 mate_node.material = mate 201 202 if "CM3D2 Shade" in bpy.context.blend_data.materials: 203 shade_mate = bpy.context.blend_data.materials["CM3D2 Shade"] 204 else: 205 shade_mate = bpy.context.blend_data.materials.new("CM3D2 Shade") 206 shade_mate.diffuse_color = (1, 1, 1) 207 shade_mate.diffuse_intensity = 1 208 shade_mate.specular_intensity = 1 209 shade_mate_node = node_tree.nodes.new('ShaderNodeExtendedMaterial') 210 shade_mate_node.location = (234.7785, -131.8243) 211 shade_mate_node.material = shade_mate 212 213 toon_node = node_tree.nodes.new('ShaderNodeValToRGB') 214 toon_node.location = (571.3662, -381.0965) 215 toon_img = is_textured[1].image 216 toon_w, toon_h = toon_img.size[0], toon_img.size[1] 217 for i in range(32 - 2): 218 toon_node.color_ramp.elements.new(0.0) 219 for i in range(32): 220 pos = i / (32 - 1) 221 toon_node.color_ramp.elements[i].position = pos 222 x = int((toon_w / (32 - 1)) * i) 223 pixel_index = x * toon_img.channels 224 toon_node.color_ramp.elements[i].color = toon_img.pixels[pixel_index: pixel_index + 4] 225 toon_node.color_ramp.interpolation = 'EASE' 226 227 shadow_rate_node = node_tree.nodes.new('ShaderNodeValToRGB') 228 shadow_rate_node.location = (488.2785, 7.8446) 229 shadow_rate_img = is_textured[3].image 230 shadow_rate_w, shadow_rate_h = shadow_rate_img.size[0], shadow_rate_img.size[1] 231 for i in range(32 - 2): 232 shadow_rate_node.color_ramp.elements.new(0.0) 233 for i in range(32): 234 pos = i / (32 - 1) 235 shadow_rate_node.color_ramp.elements[i].position = pos 236 x = int((shadow_rate_w / (32)) * i) 237 pixel_index = x * shadow_rate_img.channels 238 shadow_rate_node.color_ramp.elements[i].color = shadow_rate_img.pixels[pixel_index: pixel_index + 4] 239 shadow_rate_node.color_ramp.interpolation = 'EASE' 240 241 geometry_node = node_tree.nodes.new('ShaderNodeGeometry') 242 geometry_node.location = (323.4597, -810.8045) 243 244 shadow_texture_node = node_tree.nodes.new('ShaderNodeTexture') 245 shadow_texture_node.location = (626.0117, -666.0227) 246 shadow_texture_node.texture = is_textured[2] 247 248 invert_node = node_tree.nodes.new('ShaderNodeInvert') 249 invert_node.location = (805.6814, -132.9144) 250 251 shadow_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 252 shadow_mix_node.location = (1031.2714, -201.5598) 253 254 toon_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 255 toon_mix_node.location = (1257.5538, -308.8037) 256 toon_mix_node.blend_type = 'MULTIPLY' 257 toon_mix_node.inputs[0].default_value = 1.0 258 259 specular_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 260 specular_mix_node.location = (1473.2079, -382.7421) 261 specular_mix_node.blend_type = 'SCREEN' 262 specular_mix_node.inputs[0].default_value = mate.specular_intensity 263 264 normal_node = node_tree.nodes.new('ShaderNodeNormal') 265 normal_node.location = (912.1372, -590.8748) 266 267 rim_ramp_node = node_tree.nodes.new('ShaderNodeValToRGB') 268 rim_ramp_node.location = (1119.0664, -570.0284) 269 rim_ramp_node.color_ramp.elements[0].color = list(rimcolor[:]) + [1.0] 270 rim_ramp_node.color_ramp.elements[0].position = rimshift 271 rim_ramp_node.color_ramp.elements[1].color = (0, 0, 0, 1) 272 rim_ramp_node.color_ramp.elements[1].position = (rimshift) + ((1.0 - (rimpower * 0.03333)) * 0.5) 273 274 rim_power_node = node_tree.nodes.new('ShaderNodeHueSaturation') 275 rim_power_node.location = (1426.6332, -575.6142) 276 # rim_power_node.inputs[2].default_value = rimpower * 0.1 277 278 rim_mix_node = node_tree.nodes.new('ShaderNodeMixRGB') 279 rim_mix_node.location = (1724.7024, -451.9624) 280 rim_mix_node.blend_type = 'ADD' 281 282 out_node = node_tree.nodes.new('ShaderNodeOutput') 283 out_node.location = (1957.4023, -480.5365) 284 285 node_tree.links.new(shadow_mix_node.inputs[1], mate_node.outputs[0]) 286 node_tree.links.new(shadow_rate_node.inputs[0], shade_mate_node.outputs[3]) 287 node_tree.links.new(invert_node.inputs[1], shadow_rate_node.outputs[0]) 288 node_tree.links.new(shadow_mix_node.inputs[0], invert_node.outputs[0]) 289 node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3]) 290 node_tree.links.new(shadow_texture_node.inputs[0], geometry_node.outputs[4]) 291 node_tree.links.new(shadow_mix_node.inputs[2], shadow_texture_node.outputs[1]) 292 node_tree.links.new(toon_node.inputs[0], shade_mate_node.outputs[3]) 293 node_tree.links.new(toon_mix_node.inputs[1], shadow_mix_node.outputs[0]) 294 node_tree.links.new(toon_mix_node.inputs[2], toon_node.outputs[0]) 295 node_tree.links.new(specular_mix_node.inputs[1], toon_mix_node.outputs[0]) 296 node_tree.links.new(specular_mix_node.inputs[2], shade_mate_node.outputs[4]) 297 node_tree.links.new(normal_node.inputs[0], mate_node.outputs[2]) 298 node_tree.links.new(rim_ramp_node.inputs[0], normal_node.outputs[1]) 299 node_tree.links.new(rim_power_node.inputs[4], rim_ramp_node.outputs[0]) 300 node_tree.links.new(rim_mix_node.inputs[2], rim_power_node.outputs[0]) 301 node_tree.links.new(rim_mix_node.inputs[0], shadow_rate_node.outputs[0]) 302 node_tree.links.new(rim_mix_node.inputs[1], specular_mix_node.outputs[0]) 303 node_tree.links.new(out_node.inputs[0], rim_mix_node.outputs[0]) 304 node_tree.links.new(out_node.inputs[1], mate_node.outputs[1]) 305 306 for node in node_tree.nodes[:]: 307 compat.set_select(node, False) 308 node_tree.nodes.active = mate_node 309 node_tree.nodes.active.select = True 310 311 else: 312 mate.use_nodes = False 313 mate.use_shadeless = False
def
decorate_material(mate, enable=True, me=None, mate_index=-1):
316def decorate_material(mate, enable=True, me=None, mate_index=-1): 317 if not enable or 'shader1' not in mate: 318 return 319 if compat.IS_LEGACY: 320 return decorate_material_old(mate, enable=enable, me=me, mate_index=mate_index) 321 322 # luvoid : set properties of the mate 323 shader = mate['shader1'] 324 mate.preview_render_type = 'FLAT' 325 mate.blend_method = 'BLEND' if 'Trans' in shader else 'OPAQUE' 326 mate.use_backface_culling = 'Outline' not in shader 327 328 # luvoid : create cm3d2 shader node group and material output node 329 cmnode = None 330 if not compat.IS_LEGACY: 331 mate.use_nodes = True 332 #cm3d2_data.clear_nodes(mate.node_tree.nodes) 333 cmtree = bpy.data.node_groups.get('CM3D2 Shader') 334 if not cmtree: 335 blend_path = os.path.join(os.path.dirname(__file__), "append_data.blend") 336 with bpy.data.libraries.load(blend_path) as (data_from, data_to): 337 data_to.node_groups = ['CM3D2 Shader'] 338 cmtree = data_to.node_groups[0] 339 cmnode = mate.node_tree.nodes.new('ShaderNodeGroup') 340 cmnode.node_tree = cmtree 341 matout = mate.node_tree.nodes.new('ShaderNodeOutputMaterial') 342 matout.location = (300,0) 343 mate.node_tree.links.new(matout.inputs.get('Surface'), cmnode.outputs.get('Surface')) 344 345 for key, node in mate.node_tree.nodes.items(): 346 if not key.startswith('_'): 347 continue 348 if type(node) == bpy.types.ShaderNodeTexImage: 349 # luvoid : attatch tex node to cmnode sockets 350 socket = cmnode.inputs.get(key+" Color") 351 if socket: 352 mate.node_tree.links.new(socket, node.outputs.get('Color')) 353 socket = cmnode.inputs.get(key+" Alpha") 354 if socket: 355 mate.node_tree.links.new(socket, node.outputs.get('Alpha')) 356 else: 357 # luvoid : attatch color/float node to cmnode socket 358 input_socket = cmnode.inputs.get(key) 359 output_socket = node.outputs.get('Color') or node.outputs.get('Value') 360 if input_socket and output_socket: 361 mate.node_tree.links.new(input_socket, output_socket)
def
get_image_average_color(img, sample_count=10):
367def get_image_average_color(img, sample_count=10): 368 if not len(img.pixels): 369 return mathutils.Color([0, 0, 0]) 370 371 pixel_count = img.size[0] * img.size[1] 372 channels = img.channels 373 374 max_s = 0.0 375 max_s_color, average_color = mathutils.Color([0, 0, 0]), mathutils.Color([0, 0, 0]) 376 seek_interval = pixel_count / sample_count 377 for sample_index in range(sample_count): 378 379 index = int(seek_interval * sample_index) * channels 380 color = mathutils.Color(img.pixels[index: index + 3]) 381 average_color += color 382 if max_s < color.s: 383 max_s_color, max_s = color, color.s 384 385 average_color /= sample_count 386 output_color = (average_color + max_s_color) / 2 387 output_color.s *= 1.5 388 return max_s_color
def
get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10):
392def get_image_average_color_uv(img, me=None, mate_index=-1, sample_count=10): 393 if not len(img.pixels): return mathutils.Color([0, 0, 0]) 394 395 img_width, img_height, img_channel = img.size[0], img.size[1], img.channels 396 397 bm = bmesh.new() 398 bm.from_mesh(me) 399 uv_lay = bm.loops.layers.uv.active 400 uvs = [l[uv_lay].uv[:] for f in bm.faces if f.material_index == mate_index for l in f.loops] 401 bm.free() 402 403 if len(uvs) <= sample_count: 404 return get_image_average_color(img) 405 406 average_color = mathutils.Color([0, 0, 0]) 407 max_s = 0.0 408 max_s_color = mathutils.Color([0, 0, 0]) 409 seek_interval = len(uvs) / sample_count 410 for sample_index in range(sample_count): 411 412 uv_index = int(seek_interval * sample_index) 413 x, y = uvs[uv_index] 414 415 x = math.modf(x)[0] 416 if x < 0.0: 417 x += 1.0 418 y = math.modf(y)[0] 419 if y < 0.0: 420 y += 1.0 421 422 x, y = int(x * img_width), int(y * img_height) 423 424 pixel_index = ((y * img_width) + x) * img_channel 425 color = mathutils.Color(img.pixels[pixel_index: pixel_index + 3]) 426 427 average_color += color 428 if max_s < color.s: 429 max_s_color, max_s = color, color.s 430 431 average_color /= sample_count 432 output_color = (average_color + max_s_color) / 2 433 output_color.s *= 1.5 434 return output_color
def
get_cm3d2_dir():
def
get_com3d2_dir():
def
default_cm3d2_dir(base_dir, file_name, new_ext):
456def default_cm3d2_dir(base_dir, file_name, new_ext): 457 if not base_dir: 458 prefs = preferences() 459 if prefs.cm3d2_path: 460 base_dir = os.path.join(prefs.cm3d2_path, "GameData", "*." + new_ext) 461 else: 462 base_dir = get_cm3d2_dir() 463 if base_dir is None: 464 base_dir = get_com3d2_dir() 465 466 if base_dir: 467 prefs.cm3d2_path = base_dir 468 base_dir = os.path.join(base_dir, "GameData", "*." + new_ext) 469 470 if base_dir is None: 471 base_dir = "." 472 473 if file_name: 474 base_dir = os.path.join(os.path.split(base_dir)[0], file_name) 475 base_dir = os.path.splitext(base_dir)[0] + "." + new_ext 476 return base_dir
def
open_temporary(filepath, mode, is_backup=False):
480def open_temporary(filepath, mode, is_backup=False): 481 backup_ext = preferences().backup_ext 482 if is_backup and backup_ext: 483 backup_filepath = filepath + '.' + backup_ext 484 else: 485 backup_filepath = None 486 return fileutil.TemporaryFileWriter(filepath, mode, backup_filepath=backup_filepath)
def
file_backup(filepath, enable=True):
def
find_tex_all_files(dir):
def
get_default_tex_paths():
506def get_default_tex_paths(): 507 prefs = preferences() 508 default_paths = [prefs.default_tex_path0, prefs.default_tex_path1, prefs.default_tex_path2, prefs.default_tex_path3] 509 if not any(default_paths): 510 target_dirs = [] 511 cm3d2_dir = prefs.cm3d2_path 512 if not cm3d2_dir: 513 cm3d2_dir = get_cm3d2_dir() 514 515 if cm3d2_dir: 516 target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture")) 517 target_dirs.append(os.path.join(cm3d2_dir, "GameData", "texture2")) 518 target_dirs.append(os.path.join(cm3d2_dir, "Sybaris", "GameData")) 519 target_dirs.append(os.path.join(cm3d2_dir, "Mod")) 520 521 # com3d2_dir = prefs.com3d2_path 522 # if not com3d2_dir: 523 # com3d2_dir = get_cm3d2_dir() 524 # if com3d2_dir: 525 # target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts")) 526 # target_dirs.append(os.path.join(com3d2_dir, "GameData", "parts2")) 527 # target_dirs.append(os.path.join(com3d2_dir, "MOD")) 528 529 tex_dirs = [path for path in target_dirs if os.path.isdir(path)] 530 531 for index, path in enumerate(tex_dirs): 532 setattr(prefs, 'default_tex_path' + str(index), path) 533 else: 534 tex_dirs = [getattr(prefs, 'default_tex_path' + str(i)) for i in range(4) if getattr(prefs, 'default_tex_path' + str(i))] 535 return tex_dirs
def
get_tex_storage_files():
def
get_texpath_dict(reload=False):
548def get_texpath_dict(reload=False): 549 if reload or len(texpath_dict) == 0: 550 texpath_dict.clear() 551 tex_dirs = get_default_tex_paths() 552 for tex_dir in tex_dirs: 553 for path in find_tex_all_files(tex_dir): 554 path = bpy.path.abspath(path) 555 file_name = os.path.basename(path).lower() 556 # 先に見つけたファイルを優先 557 if file_name not in texpath_dict: 558 texpath_dict[file_name] = path 559 return texpath_dict
def
reload_png(img, texpath_dict, png_name):
def
replace_cm3d2_tex(img, texpath_dict: dict = None, reload_path: bool = True) -> bool:
571def replace_cm3d2_tex(img, texpath_dict: dict=None, reload_path: bool=True) -> bool: 572 """replace tex file. 573 pngファイルを先に走査し、見つからなければtexファイルを探す. 574 texはpngに展開して読み込みを行う. 575 reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、 576 再度検索を行う. 577 578 Parameters: 579 img (Image): イメージオブジェクト 580 texpath_dict (dict): テクスチャパスのdict (キャッシュ) 581 reload_path (bool): 見つからない場合にキャッシュを再読込するか 582 583 Returns: 584 bool: tex load successful 585 """ 586 if texpath_dict is None: 587 texpath_dict = get_texpath_dict() 588 589 if __replace_cm3d2_tex(img, texpath_dict): 590 return True 591 if reload_path: 592 texpath_dict = get_texpath_dict(True) 593 return __replace_cm3d2_tex(img, texpath_dict) 594 return False
replace tex file. pngファイルを先に走査し、見つからなければtexファイルを探す. texはpngに展開して読み込みを行う. reload_path=Trueの場合、png,texファイルが見つからない場合にキャッシュを再構成し、 再度検索を行う.
Parameters: img (Image): イメージオブジェクト texpath_dict (dict): テクスチャパスのdict (キャッシュ) reload_path (bool): 見つからない場合にキャッシュを再読込するか
Returns: bool: tex load successful
def
load_cm3d2tex(path, skip_data=False):
625def load_cm3d2tex(path, skip_data=False): 626 627 with open(path, 'rb') as file: 628 header_ext = read_str(file) 629 if header_ext != 'CM3D2_TEX': 630 return None 631 version = struct.unpack('<i', file.read(4))[0] 632 read_str(file) 633 634 # default value 635 tex_format = 5 636 uv_rects = None 637 data = None 638 if version >= 1010: 639 if version >= 1011: 640 num_rect = struct.unpack('<i', file.read(4))[0] 641 uv_rects = [] 642 for i in range(num_rect): 643 # x, y, w, h 644 uv_rects.append(struct.unpack('<4f', file.read(4 * 4))) 645 width = struct.unpack('<i', file.read(4))[0] 646 height = struct.unpack('<i', file.read(4))[0] 647 tex_format = struct.unpack('<i', file.read(4))[0] 648 # if tex_format == 10 or tex_format == 12: return None 649 if not skip_data: 650 png_size = struct.unpack('<i', file.read(4))[0] 651 data = file.read(png_size) 652 return version, tex_format, uv_rects, data
def
create_tex( context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1):
655def create_tex(context, mate, node_name, tex_name=None, filepath=None, cm3d2path=None, tex_map_data=None, replace_tex=False, slot_index=-1): 656 if isinstance(context, bpy.types.Context): 657 context = context.copy() 658 659 if compat.IS_LEGACY: 660 slot = mate.texture_slots.create(slot_index) 661 tex = context['blend_data'].textures.new(node_name, 'IMAGE') 662 slot.texture = tex 663 664 if tex_name: 665 slot.offset[0] = tex_map_data[0] 666 slot.offset[1] = tex_map_data[1] 667 slot.scale[0] = tex_map_data[2] 668 slot.scale[1] = tex_map_data[3] 669 670 if os.path.exists(filepath): 671 img = bpy.data.images.load(filepath) 672 img.name = tex_name 673 else: 674 img = context['blend_data'].images.new(tex_name, 128, 128) 675 img.filepath = filepath 676 img['cm3d2_path'] = cm3d2path 677 img.source = 'FILE' 678 tex.image = img 679 680 if replace_tex: 681 replaced = replace_cm3d2_tex(tex.image, reload_path=False) 682 if replaced and node_name == '_MainTex': 683 ob = context['active_object'] 684 me = ob.data 685 for face in me.polygons: 686 if face.material_index == ob.active_material_index: 687 me.uv_textures.active.data[face.index].image = tex.image 688 689 else: 690 # if mate.use_nodes is False: 691 # mate.use_nodes = True 692 nodes = mate.node_tree.nodes 693 tex = nodes.get(node_name) 694 if tex is None: 695 tex = mate.node_tree.nodes.new(type='ShaderNodeTexImage') 696 tex.name = tex.label = node_name 697 tex.show_texture = True 698 699 if tex_name: 700 if tex.image is None: 701 if os.path.exists(filepath): 702 img = bpy.data.images.load(filepath) 703 img.name = tex_name 704 else: 705 img = context['blend_data'].images.new(tex_name, 128, 128) 706 img.filepath = filepath 707 img.source = 'FILE' 708 tex.image = img 709 img['cm3d2_path'] = cm3d2path 710 else: 711 img = tex.image 712 path = img.get('cm3d2_path') 713 if path != cm3d2path: 714 img['cm3d2_path'] = cm3d2path 715 img.filepath = filepath 716 717 tex_map = tex.texture_mapping 718 tex_map.translation[0] = tex_map_data[0] 719 tex_map.translation[1] = tex_map_data[1] 720 tex_map.scale[0] = tex_map_data[2] 721 tex_map.scale[1] = tex_map_data[3] 722 723 # tex.color = tex_data['color'][:3] 724 # tex.outputs['Color'].default_value = tex_data['color'][:] 725 # tex.outputs['ALpha'].default_value = tex_data['color'][3] 726 727 # tex探し 728 if replace_tex: 729 replaced = replace_cm3d2_tex(tex.image, reload_path=False) 730 # TODO 2.8での実施方法を調査. shader editorで十分? 731 732 return tex
def
create_col(context, mate, node_name, color, slot_index=-1):
735def create_col(context, mate, node_name, color, slot_index=-1): 736 if isinstance(context, bpy.types.Context): 737 context = context.copy() 738 739 if compat.IS_LEGACY: 740 if slot_index >= 0: 741 mate.use_textures[slot_index] = False 742 node = mate.texture_slots.create(slot_index) 743 node.color = color[:3] 744 node.diffuse_color_factor = color[3] 745 node.use_rgb_to_intensity = True 746 tex = context['blend_data'].textures.new(node_name, 'BLEND') 747 node.texture = tex 748 node.use = False 749 else: 750 node = mate.node_tree.nodes.get(node_name) 751 if node is None: 752 node = mate.node_tree.nodes.new(type='ShaderNodeRGB') 753 node.name = node.label = node_name 754 node.outputs[0].default_value = color 755 756 return node
def
create_float(context, mate, node_name, value, slot_index=-1):
759def create_float(context, mate, node_name, value, slot_index=-1): 760 if isinstance(context, bpy.types.Context): 761 context = context.copy() 762 763 if compat.IS_LEGACY: 764 if slot_index >= 0: 765 mate.use_textures[slot_index] = False 766 node = mate.texture_slots.create(slot_index) 767 node.diffuse_color_factor = value 768 node.use_rgb_to_intensity = False 769 tex = context['blend_data'].textures.new(node_name, 'BLEND') 770 node.texture = tex 771 node.use = False 772 else: 773 node = mate.node_tree.nodes.get(node_name) 774 if node is None: 775 node = mate.node_tree.nodes.new(type='ShaderNodeValue') 776 node.name = node.label = node_name 777 node.outputs[0].default_value = value 778 779 return node
def
setup_material(mate):
def
setup_image_name(img):
791def setup_image_name(img): 792 """イメージの名前から拡張子を除外する""" 793 # consider case with serial number. ex) sample.png.001 794 img.name = re_png.sub(r'\1', img.name)
イメージの名前から拡張子を除外する
def
get_tex_cm3d2path(filepath):
def
to_cm3d2path(path):
def
set_texture_color(slot):
810def set_texture_color(slot): 811 if not slot or not slot.texture or slot.use: 812 return 813 814 slot_type = 'col' if slot.use_rgb_to_intensity else 'f' 815 tex = slot.texture 816 base_name = remove_serial_number(tex.name) 817 tex.type = 'BLEND' 818 819 if hasattr(tex, 'progression'): 820 tex.progression = 'DIAGONAL' 821 tex.use_color_ramp = True 822 tex.use_preview_alpha = True 823 elements = tex.color_ramp.elements 824 825 element_count = 4 826 if element_count < len(elements): 827 for i in range(len(elements) - element_count): 828 elements.remove(elements[-1]) 829 elif len(elements) < element_count: 830 for i in range(element_count - len(elements)): 831 elements.new(1.0) 832 833 elements[0].position, elements[1].position, elements[2].position, elements[3].position = 0.2, 0.21, 0.25, 0.26 834 835 if slot_type == 'col': 836 elements[0].color = [0.2, 1, 0.2, 1] 837 elements[-1].color = slot.color[:] + (slot.diffuse_color_factor, ) 838 if 0.3 < mathutils.Color(slot.color[:3]).v: 839 elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1] 840 else: 841 elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1] 842 843 elif slot_type == 'f': 844 elements[0].color = [0.2, 0.2, 1, 1] 845 multi = 1.0 846 if base_name == '_OutlineWidth': 847 multi = 200 848 elif base_name == '_RimPower': 849 multi = 1.0 / 30.0 850 value = slot.diffuse_color_factor * multi 851 elements[-1].color = [value, value, value, 1] 852 if 0.3 < value: 853 elements[1].color, elements[2].color = [0, 0, 0, 1], [0, 0, 0, 1] 854 else: 855 elements[1].color, elements[2].color = [1, 1, 1, 1], [1, 1, 1, 1]
def
get_request_area(context, request_type, except_types=None):
859def get_request_area(context, request_type, except_types=None): 860 if except_types is None: 861 except_types = ['VIEW_3D', 'PROPERTIES', 'INFO', compat.pref_type()] 862 863 request_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type == request_type] 864 candidate_areas = [(a, a.width * a.height) for a in context.screen.areas if a.type not in except_types] 865 866 return_areas = request_areas[:] if len(request_areas) else candidate_areas 867 if not len(return_areas): 868 return None 869 870 return_areas.sort(key=lambda i: i[1]) 871 return_area = return_areas[-1][0] 872 return_area.type = request_type 873 return return_area
def
remove_data(target_data):
877def remove_data(target_data): 878 try: 879 target_data = target_data[:] 880 except: 881 target_data = [target_data] 882 883 if compat.IS_LEGACY: 884 for data in target_data: 885 if data.__class__.__name__ == 'Object': 886 if data.name in bpy.context.scene.objects: 887 bpy.context.scene.objects.unlink(data) 888 else: 889 for data in target_data: 890 if data.__class__.__name__ == 'Object': 891 if data.name in bpy.context.scene.collection.objects: 892 bpy.context.scene.collection.objects.unlink(data) 893 894 # https://developer.blender.org/T49837 895 # によると、xxx.remove(data, do_unlink=True)で十分 896 # 897 # for data in target_data: 898 # users = getattr(data, 'users') 899 # if users and 'user_clear' in dir(data): 900 # data.user_clear() 901 902 for data in target_data: 903 for data_str in dir(bpy.data): 904 if not data_str.endswith('s'): 905 continue 906 try: 907 if data.__class__.__name__ == eval('bpy.data.%s[0].__class__.__name__' % data_str): 908 exec('bpy.data.%s.remove(data, do_unlink=True)' % data_str) 909 break 910 except: 911 pass
class
material_restore:
915class material_restore: 916 def __init__(self, ob): 917 override = bpy.context.copy() 918 override['object'] = ob 919 self.object = ob 920 921 self.slots = [slot.material if slot.material else None for slot in ob.material_slots] 922 923 self.mesh_data = [] 924 for index, slot in enumerate(ob.material_slots): 925 mesh_datum = [] 926 for face in ob.data.polygons: 927 if face.material_index == index: 928 mesh_datum.append(face.index) 929 self.mesh_data.append(mesh_datum) 930 931 for slot in ob.material_slots[:]: 932 bpy.ops.object.material_slot_remove(override) 933 934 def restore(self): 935 override = bpy.context.copy() 936 override['object'] = self.object 937 938 for slot in self.object.material_slots[:]: 939 bpy.ops.object.material_slot_remove(override) 940 941 for index, mate in enumerate(self.slots): 942 bpy.ops.object.material_slot_add(override) 943 slot = self.object.material_slots[index] 944 if slot: 945 slot.material = mate 946 for face_index in self.mesh_data[index]: 947 self.object.data.polygons[face_index].material_index = index
material_restore(ob)
916 def __init__(self, ob): 917 override = bpy.context.copy() 918 override['object'] = ob 919 self.object = ob 920 921 self.slots = [slot.material if slot.material else None for slot in ob.material_slots] 922 923 self.mesh_data = [] 924 for index, slot in enumerate(ob.material_slots): 925 mesh_datum = [] 926 for face in ob.data.polygons: 927 if face.material_index == index: 928 mesh_datum.append(face.index) 929 self.mesh_data.append(mesh_datum) 930 931 for slot in ob.material_slots[:]: 932 bpy.ops.object.material_slot_remove(override)
def
restore(self):
934 def restore(self): 935 override = bpy.context.copy() 936 override['object'] = self.object 937 938 for slot in self.object.material_slots[:]: 939 bpy.ops.object.material_slot_remove(override) 940 941 for index, mate in enumerate(self.slots): 942 bpy.ops.object.material_slot_add(override) 943 slot = self.object.material_slots[index] 944 if slot: 945 slot.material = mate 946 for face_index in self.mesh_data[index]: 947 self.object.data.polygons[face_index].material_index = index
class
hide_render_restore:
951class hide_render_restore: 952 def __init__(self, render_objects=[]): 953 try: 954 render_objects = render_objects[:] 955 except: 956 render_objects = [render_objects] 957 958 if not len(render_objects): 959 render_objects = bpy.context.selected_objects[:] 960 961 self.render_objects = render_objects[:] 962 self.render_object_names = [ob.name for ob in render_objects] 963 964 self.rendered_objects = [] 965 for ob in render_objects: 966 if ob.hide_render: 967 self.rendered_objects.append(ob) 968 ob.hide_render = False 969 970 self.hide_rendered_objects = [] 971 if compat.IS_LEGACY: 972 for ob in bpy.data.objects: 973 for layer_index, is_used in enumerate(bpy.context.scene.layers): 974 if not is_used: 975 continue 976 if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render: 977 self.hide_rendered_objects.append(ob) 978 ob.hide_render = True 979 break 980 else: 981 clct_children = bpy.context.scene.collection.children 982 for ob in bpy.data.objects: 983 if ob.name not in self.render_object_names and not ob.hide_render: 984 # ble-2.8ではlayerではなく、collectionからのリンクで判断 985 for clct in bpy.context.window.view_layer.layer_collection.children: 986 if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys(): 987 self.hide_rendered_objects.append(ob) 988 ob.hide_render = True 989 break 990 991 def restore(self): 992 for ob in self.rendered_objects: 993 ob.hide_render = True 994 for ob in self.hide_rendered_objects: 995 ob.hide_render = False
hide_render_restore(render_objects=[])
952 def __init__(self, render_objects=[]): 953 try: 954 render_objects = render_objects[:] 955 except: 956 render_objects = [render_objects] 957 958 if not len(render_objects): 959 render_objects = bpy.context.selected_objects[:] 960 961 self.render_objects = render_objects[:] 962 self.render_object_names = [ob.name for ob in render_objects] 963 964 self.rendered_objects = [] 965 for ob in render_objects: 966 if ob.hide_render: 967 self.rendered_objects.append(ob) 968 ob.hide_render = False 969 970 self.hide_rendered_objects = [] 971 if compat.IS_LEGACY: 972 for ob in bpy.data.objects: 973 for layer_index, is_used in enumerate(bpy.context.scene.layers): 974 if not is_used: 975 continue 976 if ob.layers[layer_index] and is_used and ob.name not in self.render_object_names and not ob.hide_render: 977 self.hide_rendered_objects.append(ob) 978 ob.hide_render = True 979 break 980 else: 981 clct_children = bpy.context.scene.collection.children 982 for ob in bpy.data.objects: 983 if ob.name not in self.render_object_names and not ob.hide_render: 984 # ble-2.8ではlayerではなく、collectionからのリンクで判断 985 for clct in bpy.context.window.view_layer.layer_collection.children: 986 if clct.exclude is False and ob.name in clct_children[clct.name].objects.keys(): 987 self.hide_rendered_objects.append(ob) 988 ob.hide_render = True 989 break
def
set_area_space_attr(area, attr_name, value):
def
in_out_quad_blend(f):
def
bezier_blend(f):
def
trigonometric_smooth(x):
class
CM3D2ExportException(builtins.Exception):
Common base class for all non-exit exceptions.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- args
class
CM3D2ImportException(builtins.Exception):
Common base class for all non-exit exceptions.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- args
class
NodeHandler:
1035class NodeHandler(): 1036 node_name = bpy.props.StringProperty(name='NodeName') 1037 1038 def get_node(self, context): 1039 mate = context.material 1040 if mate and mate.use_nodes: 1041 return mate.node_tree.nodes.get(self.node_name) 1042 1043 # if node is None: 1044 # # 見つからない場合は、シリアル番号付きのノードを探す 1045 # prefix = self.node_name + '.' 1046 # for n in nodes: 1047 # if n.name.startwith(prefix): 1048 # node = n 1049 # break 1050 1051 return None
node_name =
<_PropertyDeferred, <built-in function StringProperty>, {'name': 'NodeName', 'attr': 'node_name'}>
def
get_node(self, context):
1038 def get_node(self, context): 1039 mate = context.material 1040 if mate and mate.use_nodes: 1041 return mate.node_tree.nodes.get(self.node_name) 1042 1043 # if node is None: 1044 # # 見つからない場合は、シリアル番号付きのノードを探す 1045 # prefix = self.node_name + '.' 1046 # for n in nodes: 1047 # if n.name.startwith(prefix): 1048 # node = n 1049 # break 1050 1051 return None
class
CNV_UL_generic_selector(bpy_types.UIList):
1054class CNV_UL_generic_selector(bpy.types.UIList): 1055 bl_label = "CNV_UL_generic_selector" 1056 bl_options = {'DEFAULT_CLOSED'} 1057 bl_region_type = 'WINDOW' 1058 bl_space_type = 'PROPERTIES' 1059 1060 # Constants (flags) 1061 # Be careful not to shadow FILTER_ITEM! 1062 #bitflag_soft_filter = 1073741824 >> 0 1063 bitflag_soft_filter = 1073741824 >> 3 1064 1065 bitflag_forced_value = 1073741824 >> 10 1066 bitflag_forced_true = 1073741824 >> 11 1067 bitflag_forced_false = 1073741824 >> 12 1068 1069 1070 cached_values = {} 1071 expanded_layout = False 1072 1073 # Custom properties, saved with .blend file. 1074 use_filter_name_reverse = bpy.props.BoolProperty( 1075 name="Reverse Name", 1076 default=False, 1077 options=set(), 1078 description="Reverse name filtering", 1079 ) 1080 #use_filter_deform = bpy.props.BoolProperty( 1081 # name="Only Deform", 1082 # default=True, 1083 # options=set(), 1084 # description="Only show deforming vertex groups", 1085 #) 1086 #use_filter_deform_reverse = bpy.props.BoolProperty( 1087 # name="Other", 1088 # default=False, 1089 # options=set(), 1090 # description="Only show non-deforming vertex groups", 1091 #) 1092 #use_filter_empty = bpy.props.BoolProperty( 1093 # name="Filter Empty", 1094 # default=False, 1095 # options=set(), 1096 # description="Whether to filter empty vertex groups", 1097 #) 1098 #use_filter_empty_reverse = bpy.props.BoolProperty( 1099 # name="Reverse Empty", 1100 # default=False, 1101 # options=set(), 1102 # description="Reverse empty filtering", 1103 #) 1104 1105 # This allows us to have mutually exclusive options, which are also all disable-able! 1106 def _gen_order_update(name1, name2): 1107 def _u(self, ctxt): 1108 if (getattr(self, name1)): 1109 setattr(self, name2, False) 1110 return _u 1111 use_order_name = bpy.props.BoolProperty( 1112 name="Name", default=False, options=set(), 1113 description="Sort groups by their name (case-insensitive)", 1114 update=_gen_order_update("use_order_name", "use_order_importance"), 1115 ) 1116 use_filter_orderby_invert = bpy.props.BoolProperty( 1117 name="Order by Invert", 1118 default=False, 1119 options=set(), 1120 description="Invert the sort by order" 1121 ) 1122 #use_order_importance = bpy.props.BoolProperty( 1123 # name="Importance", 1124 # default=False, 1125 # options=set(), 1126 # description="Sort groups by their average weight in the mesh", 1127 # update=_gen_order_update("use_order_importance", "use_order_name"), 1128 #) 1129 1130 # Usual draw item function. 1131 def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag): 1132 # Just in case, we do not use it here! 1133 self.use_filter_invert = False 1134 1135 # assert(isinstance(item, bpy.types.VertexGroup) 1136 #vgroup = getattr(data, 'matched_vgroups')[item.index] 1137 if self.layout_type in {'DEFAULT', 'COMPACT'}: 1138 # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag 1139 # parameter, which contains exactly what filter_items set in its filter list for this item! 1140 # In this case, we show empty groups grayed out. 1141 cached_value = self.cached_values.get(item.name, None) 1142 if (cached_value != None) and (cached_value != item.value): 1143 item.preferred = item.value 1144 1145 force_values = flt_flag & self.bitflag_forced_value 1146 print("GET force_values =", force_values) 1147 if force_values: 1148 print("FORCE VALUES") 1149 if flt_flag & self.bitflag_forced_true: 1150 item.value = True 1151 elif flt_flag & self.bitflag_forced_false: 1152 item.value = False 1153 else: 1154 item.value = item.preferred 1155 1156 self.cached_values[item.name] = item.value 1157 1158 if flt_flag & self.bitflag_soft_filter: 1159 row = layout.row() 1160 row.enabled = False 1161 #row.alignment = 'LEFT' 1162 row.prop(item, "value", text=item.name, icon=item.icon) 1163 else: 1164 layout.prop(item, "value", text=item.name, icon=item.icon) 1165 1166 #layout.prop(item, "value", text=item.name, icon=item.icon) 1167 icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF' 1168 layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False) 1169 1170 elif self.layout_type in {'GRID'}: 1171 layout.alignment = 'CENTER' 1172 if flt_flag & self.VGROUP_EMPTY: 1173 layout.enabled = False 1174 layout.label(text="", icon_value=icon) 1175 1176 def draw_filter(self, context, layout): 1177 # Nothing much to say here, it's usual UI code... 1178 row = layout.row() 1179 if not self.expanded_layout: 1180 layout.active = True 1181 layout.enabled = True 1182 row.active = True 1183 row.enabled = True 1184 self.expanded_layout = True 1185 1186 subrow = row.row(align=True) 1187 subrow.prop(self, "filter_name", text="") 1188 icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN' 1189 subrow.prop(self, "use_filter_name_reverse", text="", icon=icon) 1190 1191 #subrow = row.row(align=True) 1192 #subrow.prop(self, "use_filter_deform", toggle=True) 1193 #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN' 1194 #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon) 1195 1196 #subrow = row.row(align=True) 1197 #subrow.prop(self, "use_filter_empty", toggle=True) 1198 #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN' 1199 #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon) 1200 1201 row = layout.row(align=True) 1202 row.label(text="Order by:") 1203 row.prop(self, "use_order_name", toggle=True) 1204 #row.prop(self, "use_order_importance", toggle=True) 1205 icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN' 1206 row.prop(self, "use_filter_orderby_invert", text="", icon=icon) 1207 1208 def filter_items(self, context, data, propname): 1209 # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists: 1210 # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the 1211 # matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the 1212 # first one to mark VGROUP_EMPTY. 1213 # * The second one is for reordering, it must return a list containing the new indices of the items (which 1214 # gives us a mapping org_idx -> new_idx). 1215 # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info). 1216 # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than 1217 # returning full lists doing nothing!). 1218 items = getattr(data, propname) 1219 1220 #if self.armature == None: 1221 # target_ob, source_ob = common.get_target_and_source_ob(context) 1222 # armature_ob = target_ob.find_armature() or source_ob.find_armature() 1223 # self.armature = armature_ob and armature_ob.data or False 1224 # 1225 #if not self.local_bone_names: 1226 # target_ob, source_ob = common.get_target_and_source_ob(context) 1227 # bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None 1228 # if bone_data_ob: 1229 # local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:")) 1230 # self.local_bone_names = [ bone['name'] for bone in local_bone_data ] 1231 1232 if not self.cached_values: 1233 self.cached_values = { item.name: item.value for item in items } 1234 #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0] for item in items ] 1235 helper_funcs = bpy.types.UI_UL_list 1236 1237 # Default return values. 1238 flt_flags = [] 1239 flt_neworder = [] 1240 1241 # Pre-compute of vgroups data, CPU-intensive. :/ 1242 #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups) 1243 1244 # Filtering by name 1245 if self.filter_name: 1246 flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name", 1247 reverse=self.use_filter_name_reverse) 1248 if not flt_flags: 1249 flt_flags = [self.bitflag_filter_item] * len(items) 1250 1251 #for idx, vg in enumerate(items): 1252 # # Filter by deform. 1253 # if self.use_filter_deform: 1254 # flt_flags[idx] |= self.VGROUP_DEFORM 1255 # if self.use_filter_deform: 1256 # if self.armature and self.armature.get(vg.name): 1257 # if not self.use_filter_deform_reverse: 1258 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1259 # elif bone_data_ob and (vg.name in self.local_bone_names): 1260 # if not self.use_filter_deform_reverse: 1261 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1262 # elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names): 1263 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1264 # else: 1265 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1266 # 1267 # # Filter by emptiness. 1268 # #if vgroups_empty[vg.index][0]: 1269 # # flt_flags[idx] |= self.VGROUP_EMPTY 1270 # # if self.use_filter_empty and self.use_filter_empty_reverse: 1271 # # flt_flags[idx] &= ~self.bitflag_filter_item 1272 # #elif self.use_filter_empty and not self.use_filter_empty_reverse: 1273 # # flt_flags[idx] &= ~self.bitflag_filter_item 1274 1275 # Reorder by name or average weight. 1276 if self.use_order_name: 1277 flt_neworder = helper_funcs.sort_items_by_name(items, "name") 1278 #elif self.use_order_importance: 1279 # _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)] 1280 # flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True) 1281 1282 return flt_flags, flt_neworder
use_filter_name_reverse =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Reverse Name', 'default': False, 'options': set(), 'description': 'Reverse name filtering'}>
use_order_name =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Name', 'default': False, 'options': set(), 'description': 'Sort groups by their name (case-insensitive)', 'update': <function CNV_UL_generic_selector._gen_order_update.<locals>._u>}>
use_filter_orderby_invert =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Order by Invert', 'default': False, 'options': set(), 'description': 'Invert the sort by order'}>
def
draw_item( self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag):
1131 def draw_item(self, context, layout, data, item, icon_value, active_data, active_propname, index, flt_flag): 1132 # Just in case, we do not use it here! 1133 self.use_filter_invert = False 1134 1135 # assert(isinstance(item, bpy.types.VertexGroup) 1136 #vgroup = getattr(data, 'matched_vgroups')[item.index] 1137 if self.layout_type in {'DEFAULT', 'COMPACT'}: 1138 # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag 1139 # parameter, which contains exactly what filter_items set in its filter list for this item! 1140 # In this case, we show empty groups grayed out. 1141 cached_value = self.cached_values.get(item.name, None) 1142 if (cached_value != None) and (cached_value != item.value): 1143 item.preferred = item.value 1144 1145 force_values = flt_flag & self.bitflag_forced_value 1146 print("GET force_values =", force_values) 1147 if force_values: 1148 print("FORCE VALUES") 1149 if flt_flag & self.bitflag_forced_true: 1150 item.value = True 1151 elif flt_flag & self.bitflag_forced_false: 1152 item.value = False 1153 else: 1154 item.value = item.preferred 1155 1156 self.cached_values[item.name] = item.value 1157 1158 if flt_flag & self.bitflag_soft_filter: 1159 row = layout.row() 1160 row.enabled = False 1161 #row.alignment = 'LEFT' 1162 row.prop(item, "value", text=item.name, icon=item.icon) 1163 else: 1164 layout.prop(item, "value", text=item.name, icon=item.icon) 1165 1166 #layout.prop(item, "value", text=item.name, icon=item.icon) 1167 icon = 'RADIOBUT_ON' if item.preferred else 'RADIOBUT_OFF' 1168 layout.prop(item, "preferred", text="", icon=compat.icon(icon), emboss=False) 1169 1170 elif self.layout_type in {'GRID'}: 1171 layout.alignment = 'CENTER' 1172 if flt_flag & self.VGROUP_EMPTY: 1173 layout.enabled = False 1174 layout.label(text="", icon_value=icon)
def
draw_filter(self, context, layout):
1176 def draw_filter(self, context, layout): 1177 # Nothing much to say here, it's usual UI code... 1178 row = layout.row() 1179 if not self.expanded_layout: 1180 layout.active = True 1181 layout.enabled = True 1182 row.active = True 1183 row.enabled = True 1184 self.expanded_layout = True 1185 1186 subrow = row.row(align=True) 1187 subrow.prop(self, "filter_name", text="") 1188 icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN' 1189 subrow.prop(self, "use_filter_name_reverse", text="", icon=icon) 1190 1191 #subrow = row.row(align=True) 1192 #subrow.prop(self, "use_filter_deform", toggle=True) 1193 #icon = 'ZOOM_OUT' if self.use_filter_deform_reverse else 'ZOOM_IN' 1194 #subrow.prop(self, "use_filter_deform_reverse", text="", icon=icon) 1195 1196 #subrow = row.row(align=True) 1197 #subrow.prop(self, "use_filter_empty", toggle=True) 1198 #icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN' 1199 #subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon) 1200 1201 row = layout.row(align=True) 1202 row.label(text="Order by:") 1203 row.prop(self, "use_order_name", toggle=True) 1204 #row.prop(self, "use_order_importance", toggle=True) 1205 icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN' 1206 row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
def
filter_items(self, context, data, propname):
1208 def filter_items(self, context, data, propname): 1209 # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists: 1210 # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the 1211 # matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the 1212 # first one to mark VGROUP_EMPTY. 1213 # * The second one is for reordering, it must return a list containing the new indices of the items (which 1214 # gives us a mapping org_idx -> new_idx). 1215 # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info). 1216 # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than 1217 # returning full lists doing nothing!). 1218 items = getattr(data, propname) 1219 1220 #if self.armature == None: 1221 # target_ob, source_ob = common.get_target_and_source_ob(context) 1222 # armature_ob = target_ob.find_armature() or source_ob.find_armature() 1223 # self.armature = armature_ob and armature_ob.data or False 1224 # 1225 #if not self.local_bone_names: 1226 # target_ob, source_ob = common.get_target_and_source_ob(context) 1227 # bone_data_ob = (target_ob.get("LocalBoneData:0") and target_ob) or (source_ob.get("LocalBoneData:0") and source_ob) or None 1228 # if bone_data_ob: 1229 # local_bone_data = model_export.CNV_OT_export_cm3d2_model.local_bone_data_parser(model_export.CNV_OT_export_cm3d2_model.indexed_data_generator(bone_data_ob, prefix="LocalBoneData:")) 1230 # self.local_bone_names = [ bone['name'] for bone in local_bone_data ] 1231 1232 if not self.cached_values: 1233 self.cached_values = { item.name: item.value for item in items } 1234 #vgroups = [ getattr(data, 'matched_vgroups')[item.index][0] for item in items ] 1235 helper_funcs = bpy.types.UI_UL_list 1236 1237 # Default return values. 1238 flt_flags = [] 1239 flt_neworder = [] 1240 1241 # Pre-compute of vgroups data, CPU-intensive. :/ 1242 #vgroups_empty = self.filter_items_empty_vgroups(context, vgroups) 1243 1244 # Filtering by name 1245 if self.filter_name: 1246 flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name", 1247 reverse=self.use_filter_name_reverse) 1248 if not flt_flags: 1249 flt_flags = [self.bitflag_filter_item] * len(items) 1250 1251 #for idx, vg in enumerate(items): 1252 # # Filter by deform. 1253 # if self.use_filter_deform: 1254 # flt_flags[idx] |= self.VGROUP_DEFORM 1255 # if self.use_filter_deform: 1256 # if self.armature and self.armature.get(vg.name): 1257 # if not self.use_filter_deform_reverse: 1258 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1259 # elif bone_data_ob and (vg.name in self.local_bone_names): 1260 # if not self.use_filter_deform_reverse: 1261 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1262 # elif self.use_filter_deform_reverse or (not self.armature and not self.local_bone_names): 1263 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1264 # else: 1265 # flt_flags[idx] &= ~self.VGROUP_DEFORM 1266 # 1267 # # Filter by emptiness. 1268 # #if vgroups_empty[vg.index][0]: 1269 # # flt_flags[idx] |= self.VGROUP_EMPTY 1270 # # if self.use_filter_empty and self.use_filter_empty_reverse: 1271 # # flt_flags[idx] &= ~self.bitflag_filter_item 1272 # #elif self.use_filter_empty and not self.use_filter_empty_reverse: 1273 # # flt_flags[idx] &= ~self.bitflag_filter_item 1274 1275 # Reorder by name or average weight. 1276 if self.use_order_name: 1277 flt_neworder = helper_funcs.sort_items_by_name(items, "name") 1278 #elif self.use_order_importance: 1279 # _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)] 1280 # flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True) 1281 1282 return flt_flags, flt_neworder
Inherited Members
- bpy_types.UIList
- bl_rna
- bpy_types._GenericUI
- is_extended
- append
- prepend
- remove
- builtins.bpy_struct
- keys
- values
- items
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data
@compat.BlRegister()
class
CNV_SelectorItem1286@compat.BlRegister() 1287class CNV_SelectorItem(bpy.types.PropertyGroup): 1288 bl_label = "CNV_SelectorItem" 1289 bl_region_type = 'WINDOW' 1290 bl_space_type = 'PROPERTIES' 1291 1292 name = bpy.props.StringProperty (name="Name" , default="Unknown") 1293 value = bpy.props.BoolProperty (name="Value" , default=True ) 1294 index = bpy.props.IntProperty (name="Index" , default=-1 ) 1295 preferred = bpy.props.BoolProperty (name="Prefered", default=True ) 1296 icon = bpy.props.StringProperty (name="Icon" , default='NONE' ) 1297 1298 filter0 = bpy.props.BoolProperty (name="Filter 0", default=False ) 1299 filter1 = bpy.props.BoolProperty (name="Filter 1", default=False ) 1300 filter2 = bpy.props.BoolProperty (name="Filter 2", default=False ) 1301 filter3 = bpy.props.BoolProperty (name="Filter 3", default=False )
name: <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Name', 'default': 'Unknown', 'attr': 'name'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'name': 'Name', 'default': 'Unknown', 'attr': 'name'}>
value: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Value', 'default': True, 'attr': 'value'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Value', 'default': True, 'attr': 'value'}>
index: <_PropertyDeferred, <built-in function IntProperty>, {'name': 'Index', 'default': -1, 'attr': 'index'}> =
<_PropertyDeferred, <built-in function IntProperty>, {'name': 'Index', 'default': -1, 'attr': 'index'}>
preferred: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Prefered', 'default': True, 'attr': 'preferred'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Prefered', 'default': True, 'attr': 'preferred'}>
icon: <_PropertyDeferred, <built-in function StringProperty>, {'name': 'Icon', 'default': 'NONE', 'attr': 'icon'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'name': 'Icon', 'default': 'NONE', 'attr': 'icon'}>
filter0: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 0', 'default': False, 'attr': 'filter0'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 0', 'default': False, 'attr': 'filter0'}>
filter1: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 1', 'default': False, 'attr': 'filter1'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 1', 'default': False, 'attr': 'filter1'}>
filter2: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 2', 'default': False, 'attr': 'filter2'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 2', 'default': False, 'attr': 'filter2'}>
filter3: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 3', 'default': False, 'attr': 'filter3'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Filter 3', 'default': False, 'attr': 'filter3'}>
Inherited Members
- builtins.bpy_struct
- keys
- values
- items
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data
def
values_of_matched_keys(dict1, dict2):
1308def values_of_matched_keys(dict1, dict2): 1309 value_list = [] 1310 items1 = dict1.items() 1311 items2 = dict2.items() 1312 if len(items1) <= len(items2): 1313 items1.reverse() 1314 for k1, v1 in items1: 1315 for i in range(len(items2)-1, 0-1, -1): 1316 k2, v2 = items2[i] 1317 if k1 == k2: 1318 value_list.append((v1,v2)) 1319 del items2[i] 1320 else: 1321 items2.reverse() 1322 for k2, v2 in items2: 1323 for i in range(len(items1)-1, 0-1, -1): 1324 k1, v1 = items1[i] 1325 if k1 == k2: 1326 value_list.append((v1,v2)) 1327 del items1[i] 1328 1329 value_list.reverse() 1330 return value_list
def
get_target_and_source_ob(context, copyTarget=False, copySource=False):
1334def get_target_and_source_ob(context, copyTarget=False, copySource=False): 1335 target_ob = None 1336 source_ob = None 1337 target_original_ob = None 1338 source_original_ob = None 1339 1340 target_original_ob = context.object 1341 if copyTarget: 1342 target_ob = target_original_ob.copy() 1343 target_ob.data = target_original_ob.data.copy() 1344 else: 1345 target_ob = target_original_ob 1346 1347 for ob in context.selected_objects: 1348 if ob != target_ob: 1349 source_original_ob = ob 1350 break 1351 1352 if copySource: 1353 source_ob = source_original_ob.copy() 1354 source_ob.data = source_original_ob.data.copy() 1355 else: 1356 source_ob = source_original_ob 1357 1358 if copyTarget: 1359 if copySource: 1360 return target_ob, source_ob, target_original_ob, source_original_ob 1361 else: 1362 return target_ob, source_ob, target_original_ob 1363 elif copySource: 1364 return target_ob, source_ob, source_original_ob 1365 else: 1366 return target_ob, source_ob
def
is_descendant_of(bone, ancestor) -> bool:
1370def is_descendant_of(bone, ancestor) -> bool: 1371 """Returns true if a bone is the descendant of the given ancestor""" 1372 while bone.parent: 1373 bone = bone.parent 1374 if bone.name == ancestor.name: 1375 return True 1376 return False
Returns true if a bone is the descendant of the given ancestor